Apply prettier formatting

This commit is contained in:
Michael Weimann 2022-12-12 12:24:14 +01:00
parent 1cac306093
commit 526645c791
No known key found for this signature in database
GPG key ID: 53F535A266BB9584
1576 changed files with 65385 additions and 62478 deletions

View file

@ -18,183 +18,139 @@ import { getLineAndNodePosition } from "../../src/editor/caret";
import EditorModel from "../../src/editor/model";
import { createPartCreator } from "./mock";
describe('editor/caret: DOM position for caret', function() {
describe('basic text handling', function() {
it('at end of single line', function() {
describe("editor/caret: DOM position for caret", function () {
describe("basic text handling", function () {
it("at end of single line", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 0, offset: 5 });
const model = new EditorModel([pc.plain("hello")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 0, offset: 5 });
expect(lineIndex).toBe(0);
expect(nodeIndex).toBe(0);
expect(offset).toBe(5);
});
it('at start of single line', function() {
it("at start of single line", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 0, offset: 0 });
const model = new EditorModel([pc.plain("hello")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 0, offset: 0 });
expect(lineIndex).toBe(0);
expect(nodeIndex).toBe(0);
expect(offset).toBe(0);
});
it('at middle of single line', function() {
it("at middle of single line", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 0, offset: 2 });
const model = new EditorModel([pc.plain("hello")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 0, offset: 2 });
expect(lineIndex).toBe(0);
expect(nodeIndex).toBe(0);
expect(offset).toBe(2);
});
});
describe('handling line breaks', function() {
it('at end of last line', function() {
describe("handling line breaks", function () {
it("at end of last line", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
pc.newline(),
pc.plain("world"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 2, offset: 5 });
const model = new EditorModel([pc.plain("hello"), pc.newline(), pc.plain("world")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 2, offset: 5 });
expect(lineIndex).toBe(1);
expect(nodeIndex).toBe(0);
expect(offset).toBe(5);
});
it('at start of last line', function() {
it("at start of last line", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
pc.newline(),
pc.plain("world"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 2, offset: 0 });
const model = new EditorModel([pc.plain("hello"), pc.newline(), pc.plain("world")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 2, offset: 0 });
expect(lineIndex).toBe(1);
expect(nodeIndex).toBe(0);
expect(offset).toBe(0);
});
it('in empty line', function() {
it("in empty line", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
pc.newline(),
pc.newline(),
pc.plain("world"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 1, offset: 1 });
const model = new EditorModel([pc.plain("hello"), pc.newline(), pc.newline(), pc.plain("world")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 1, offset: 1 });
expect(lineIndex).toBe(1);
expect(nodeIndex).toBe(-1);
expect(offset).toBe(0);
});
it('after empty line', function() {
it("after empty line", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
pc.newline(),
pc.newline(),
pc.plain("world"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 3, offset: 0 });
const model = new EditorModel([pc.plain("hello"), pc.newline(), pc.newline(), pc.plain("world")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 3, offset: 0 });
expect(lineIndex).toBe(2);
expect(nodeIndex).toBe(0);
expect(offset).toBe(0);
});
});
describe('handling non-editable parts and caret nodes', function() {
it('at start of non-editable part (with plain text around)', function() {
describe("handling non-editable parts and caret nodes", function () {
it("at start of non-editable part (with plain text around)", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
pc.userPill("Alice", "@alice:hs.tld"),
pc.plain("!"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 1, offset: 0 });
const model = new EditorModel(
[pc.plain("hello"), pc.userPill("Alice", "@alice:hs.tld"), pc.plain("!")],
pc,
);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 1, offset: 0 });
expect(lineIndex).toBe(0);
expect(nodeIndex).toBe(0);
expect(offset).toBe(5);
});
it('in middle of non-editable part (with plain text around)', function() {
it("in middle of non-editable part (with plain text around)", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
pc.userPill("Alice", "@alice:hs.tld"),
pc.plain("!"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 1, offset: 2 });
const model = new EditorModel(
[pc.plain("hello"), pc.userPill("Alice", "@alice:hs.tld"), pc.plain("!")],
pc,
);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 1, offset: 2 });
expect(lineIndex).toBe(0);
expect(nodeIndex).toBe(2);
expect(offset).toBe(0);
});
it('at start of non-editable part (without plain text around)', function() {
it("at start of non-editable part (without plain text around)", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.userPill("Alice", "@alice:hs.tld"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 0, offset: 0 });
const model = new EditorModel([pc.userPill("Alice", "@alice:hs.tld")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 0, offset: 0 });
expect(lineIndex).toBe(0);
//presumed nodes on line are (caret, pill, caret)
expect(nodeIndex).toBe(0);
expect(offset).toBe(0);
});
it('in middle of non-editable part (without plain text around)', function() {
it("in middle of non-editable part (without plain text around)", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.userPill("Alice", "@alice:hs.tld"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 0, offset: 1 });
const model = new EditorModel([pc.userPill("Alice", "@alice:hs.tld")], pc);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 0, offset: 1 });
expect(lineIndex).toBe(0);
//presumed nodes on line are (caret, pill, caret)
expect(nodeIndex).toBe(2);
expect(offset).toBe(0);
});
it('in middle of a first non-editable part, with another one following', function() {
it("in middle of a first non-editable part, with another one following", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.userPill("Alice", "@alice:hs.tld"),
pc.userPill("Bob", "@bob:hs.tld"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 0, offset: 1 });
const model = new EditorModel(
[pc.userPill("Alice", "@alice:hs.tld"), pc.userPill("Bob", "@bob:hs.tld")],
pc,
);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 0, offset: 1 });
expect(lineIndex).toBe(0);
//presumed nodes on line are (caret, pill, caret, pill, caret)
expect(nodeIndex).toBe(2);
expect(offset).toBe(0);
});
it('in start of a second non-editable part, with another one before it', function() {
it("in start of a second non-editable part, with another one before it", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.userPill("Alice", "@alice:hs.tld"),
pc.userPill("Bob", "@bob:hs.tld"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 1, offset: 0 });
const model = new EditorModel(
[pc.userPill("Alice", "@alice:hs.tld"), pc.userPill("Bob", "@bob:hs.tld")],
pc,
);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 1, offset: 0 });
expect(lineIndex).toBe(0);
//presumed nodes on line are (caret, pill, caret, pill, caret)
expect(nodeIndex).toBe(2);
expect(offset).toBe(0);
});
it('in middle of a second non-editable part, with another one before it', function() {
it("in middle of a second non-editable part, with another one before it", function () {
const pc = createPartCreator();
const model = new EditorModel([
pc.userPill("Alice", "@alice:hs.tld"),
pc.userPill("Bob", "@bob:hs.tld"),
], pc);
const { offset, lineIndex, nodeIndex } =
getLineAndNodePosition(model, { index: 1, offset: 1 });
const model = new EditorModel(
[pc.userPill("Alice", "@alice:hs.tld"), pc.userPill("Bob", "@bob:hs.tld")],
pc,
);
const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 1, offset: 1 });
expect(lineIndex).toBe(0);
//presumed nodes on line are (caret, pill, caret, pill, caret)
expect(nodeIndex).toBe(4);

View file

@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from 'matrix-js-sdk/src/matrix';
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { parseEvent } from "../../src/editor/deserialize";
import { createPartCreator } from "./mock";
@ -73,46 +73,46 @@ function normalize(parts) {
// plain parts are returned is an implementation detail
mergeAdjacentParts(parts);
// convert to data objects for easier asserting
return parts.map(p => p.serialize());
return parts.map((p) => p.serialize());
}
describe('editor/deserialize', function() {
describe('text messages', function() {
it('test with newlines', function() {
describe("editor/deserialize", function () {
describe("text messages", function () {
it("test with newlines", function () {
const parts = normalize(parseEvent(textMessage("hello\nworld"), createPartCreator()));
expect(parts[0]).toStrictEqual({ type: "plain", text: "hello" });
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[2]).toStrictEqual({ type: "plain", text: "world" });
expect(parts.length).toBe(3);
});
it('@room pill', function() {
it("@room pill", function () {
const parts = normalize(parseEvent(textMessage("text message for @room"), createPartCreator()));
expect(parts.length).toBe(2);
expect(parts[0]).toStrictEqual({ type: "plain", text: "text message for " });
expect(parts[1]).toStrictEqual({ type: "at-room-pill", text: "@room" });
});
it('emote', function() {
it("emote", function () {
const text = "says DON'T SHOUT!";
const parts = normalize(parseEvent(textMessage(text, "m.emote"), createPartCreator()));
expect(parts.length).toBe(1);
expect(parts[0]).toStrictEqual({ type: "plain", text: "/me says DON'T SHOUT!" });
});
});
describe('html messages', function() {
it('inline styling', function() {
describe("html messages", function () {
it("inline styling", function () {
const html = "<strong>bold</strong> and <em>emphasized</em> text";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(1);
expect(parts[0]).toStrictEqual({ type: "plain", text: "**bold** and _emphasized_ text" });
});
it('hyperlink', function() {
it("hyperlink", function () {
const html = 'click <a href="http://example.com/">this</a>!';
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(1);
expect(parts[0]).toStrictEqual({ type: "plain", text: "click [this](http://example.com/)!" });
});
it('multiple lines with paragraphs', function() {
const html = '<p>hello</p><p>world</p>';
it("multiple lines with paragraphs", function () {
const html = "<p>hello</p><p>world</p>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(4);
expect(parts[0]).toStrictEqual({ type: "plain", text: "hello" });
@ -120,16 +120,16 @@ describe('editor/deserialize', function() {
expect(parts[2]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[3]).toStrictEqual({ type: "plain", text: "world" });
});
it('multiple lines with line breaks', function() {
const html = 'hello<br>world';
it("multiple lines with line breaks", function () {
const html = "hello<br>world";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(3);
expect(parts[0]).toStrictEqual({ type: "plain", text: "hello" });
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[2]).toStrictEqual({ type: "plain", text: "world" });
});
it('multiple lines mixing paragraphs and line breaks', function() {
const html = '<p>hello<br>warm</p><p>world</p>';
it("multiple lines mixing paragraphs and line breaks", function () {
const html = "<p>hello<br>warm</p><p>world</p>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(6);
expect(parts[0]).toStrictEqual({ type: "plain", text: "hello" });
@ -139,8 +139,8 @@ describe('editor/deserialize', function() {
expect(parts[4]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[5]).toStrictEqual({ type: "plain", text: "world" });
});
it('quote', function() {
const html = '<blockquote><p><em>wise</em><br><strong>words</strong></p></blockquote><p>indeed</p>';
it("quote", function () {
const html = "<blockquote><p><em>wise</em><br><strong>words</strong></p></blockquote><p>indeed</p>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(6);
expect(parts[0]).toStrictEqual({ type: "plain", text: "> _wise_" });
@ -150,60 +150,60 @@ describe('editor/deserialize', function() {
expect(parts[4]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[5]).toStrictEqual({ type: "plain", text: "indeed" });
});
it('user pill', function() {
const html = "Hi <a href=\"https://matrix.to/#/@alice:hs.tld\">Alice</a>!";
it("user pill", function () {
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice</a>!';
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(3);
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice", resourceId: "@alice:hs.tld" });
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
});
it('user pill with displayname containing backslash', function() {
const html = "Hi <a href=\"https://matrix.to/#/@alice:hs.tld\">Alice\\</a>!";
it("user pill with displayname containing backslash", function () {
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice\\</a>!';
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(3);
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice\\", resourceId: "@alice:hs.tld" });
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
});
it('user pill with displayname containing opening square bracket', function() {
const html = "Hi <a href=\"https://matrix.to/#/@alice:hs.tld\">Alice[[</a>!";
it("user pill with displayname containing opening square bracket", function () {
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice[[</a>!';
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(3);
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice[[", resourceId: "@alice:hs.tld" });
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
});
it('user pill with displayname containing closing square bracket', function() {
const html = "Hi <a href=\"https://matrix.to/#/@alice:hs.tld\">Alice]</a>!";
it("user pill with displayname containing closing square bracket", function () {
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice]</a>!';
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(3);
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice]", resourceId: "@alice:hs.tld" });
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
});
it('room pill', function() {
const html = "Try <a href=\"https://matrix.to/#/#room:hs.tld\">#room:hs.tld</a>?";
it("room pill", function () {
const html = 'Try <a href="https://matrix.to/#/#room:hs.tld">#room:hs.tld</a>?';
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(3);
expect(parts[0]).toStrictEqual({ type: "plain", text: "Try " });
expect(parts[1]).toStrictEqual({ type: "room-pill", text: "#room:hs.tld", resourceId: "#room:hs.tld" });
expect(parts[2]).toStrictEqual({ type: "plain", text: "?" });
});
it('@room pill', function() {
it("@room pill", function () {
const html = "<em>formatted</em> message for @room";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(2);
expect(parts[0]).toStrictEqual({ type: "plain", text: "_formatted_ message for " });
expect(parts[1]).toStrictEqual({ type: "at-room-pill", text: "@room" });
});
it('inline code', function() {
it("inline code", function () {
const html = "there is no place like <code>127.0.0.1</code>!";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(1);
expect(parts[0]).toStrictEqual({ type: "plain", text: "there is no place like `127.0.0.1`!" });
});
it('code block with no trailing text', function() {
it("code block with no trailing text", function () {
const html = "<pre><code>0xDEADBEEF\n</code></pre>\n";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(5);
@ -214,7 +214,7 @@ describe('editor/deserialize', function() {
expect(parts[4]).toStrictEqual({ type: "plain", text: "```" });
});
// failing likely because of https://github.com/vector-im/element-web/issues/10316
xit('code block with no trailing text and no newlines', function() {
xit("code block with no trailing text and no newlines", function () {
const html = "<pre><code>0xDEADBEEF</code></pre>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(5);
@ -224,7 +224,7 @@ describe('editor/deserialize', function() {
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[4]).toStrictEqual({ type: "plain", text: "```" });
});
it('unordered lists', function() {
it("unordered lists", function () {
const html = "<ul><li>Oak</li><li>Spruce</li><li>Birch</li></ul>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(5);
@ -234,7 +234,7 @@ describe('editor/deserialize', function() {
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[4]).toStrictEqual({ type: "plain", text: "- Birch" });
});
it('ordered lists', function() {
it("ordered lists", function () {
const html = "<ol><li>Start</li><li>Continue</li><li>Finish</li></ol>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(5);
@ -244,7 +244,7 @@ describe('editor/deserialize', function() {
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[4]).toStrictEqual({ type: "plain", text: "3. Finish" });
});
it('nested unordered lists', () => {
it("nested unordered lists", () => {
const html = "<ul><li>Oak<ul><li>Spruce<ul><li>Birch</li></ul></li></ul></li></ul>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(5);
@ -254,7 +254,7 @@ describe('editor/deserialize', function() {
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}- Birch` });
});
it('nested ordered lists', () => {
it("nested ordered lists", () => {
const html = "<ol><li>Oak<ol><li>Spruce<ol><li>Birch</li></ol></li></ol></li></ol>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(5);
@ -264,7 +264,7 @@ describe('editor/deserialize', function() {
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}1. Birch` });
});
it('nested lists', () => {
it("nested lists", () => {
const html = "<ol><li>Oak\n<ol><li>Spruce\n<ol><li>Birch</li></ol></li></ol></li></ol>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(5);
@ -274,73 +274,73 @@ describe('editor/deserialize', function() {
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}1. Birch` });
});
it('mx-reply is stripped', function() {
it("mx-reply is stripped", function () {
const html = "<mx-reply>foo</mx-reply>bar";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts.length).toBe(1);
expect(parts[0]).toStrictEqual({ type: "plain", text: "bar" });
});
it('emote', function() {
it("emote", function () {
const html = "says <em>DON'T SHOUT</em>!";
const parts = normalize(parseEvent(htmlMessage(html, "m.emote"), createPartCreator()));
expect(parts.length).toBe(1);
expect(parts[0]).toStrictEqual({ type: "plain", text: "/me says _DON'T SHOUT_!" });
});
it('preserves nested quotes', () => {
it("preserves nested quotes", () => {
const html = "<blockquote>foo<blockquote>bar</blockquote></blockquote>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('surrounds lists with newlines', () => {
it("surrounds lists with newlines", () => {
const html = "foo<ul><li>bar</li></ul>baz";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('preserves nested formatting', () => {
it("preserves nested formatting", () => {
const html = "a<sub>b<em>c<strong>d<u>e</u></strong></em></sub>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('escapes backticks in code blocks', () => {
const html = "<p><code>this → ` is a backtick</code></p>" +
"<pre><code>and here are 3 of them:\n```</code></pre>";
it("escapes backticks in code blocks", () => {
const html =
"<p><code>this → ` is a backtick</code></p>" + "<pre><code>and here are 3 of them:\n```</code></pre>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('escapes backticks outside of code blocks', () => {
it("escapes backticks outside of code blocks", () => {
const html = "some `backticks`";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('escapes backslashes', () => {
it("escapes backslashes", () => {
const html = "C:\\My Documents";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('escapes asterisks', () => {
it("escapes asterisks", () => {
const html = "*hello*";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('escapes underscores', () => {
it("escapes underscores", () => {
const html = "__emphasis__";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('escapes square brackets', () => {
it("escapes square brackets", () => {
const html = "[not an actual link](https://example.org)";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
it('escapes angle brackets', () => {
it("escapes angle brackets", () => {
const html = "> \\<del>no formatting here\\</del>";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
expect(parts).toMatchSnapshot();
});
});
describe('plaintext messages', function() {
it('turns html tags back into markdown', function() {
const html = "<strong>bold</strong> and <em>emphasized</em> text <a href=\"http://example.com/\">this</a>!";
describe("plaintext messages", function () {
it("turns html tags back into markdown", function () {
const html = '<strong>bold</strong> and <em>emphasized</em> text <a href="http://example.com/">this</a>!';
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
expect(parts.length).toBe(1);
expect(parts[0]).toStrictEqual({
@ -348,7 +348,7 @@ describe('editor/deserialize', function() {
text: "**bold** and _emphasized_ text [this](http://example.com/)!",
});
});
it('keeps backticks unescaped', () => {
it("keeps backticks unescaped", () => {
const html = "this → ` is a backtick and here are 3 of them:\n```";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
expect(parts.length).toBe(1);
@ -357,7 +357,7 @@ describe('editor/deserialize', function() {
text: "this → ` is a backtick and here are 3 of them:\n```",
});
});
it('keeps backticks outside of code blocks', () => {
it("keeps backticks outside of code blocks", () => {
const html = "some `backticks`";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
expect(parts.length).toBe(1);
@ -366,7 +366,7 @@ describe('editor/deserialize', function() {
text: "some `backticks`",
});
});
it('keeps backslashes', () => {
it("keeps backslashes", () => {
const html = "C:\\My Documents";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
expect(parts.length).toBe(1);
@ -375,7 +375,7 @@ describe('editor/deserialize', function() {
text: "C:\\My Documents",
});
});
it('keeps asterisks', () => {
it("keeps asterisks", () => {
const html = "*hello*";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
expect(parts.length).toBe(1);
@ -384,7 +384,7 @@ describe('editor/deserialize', function() {
text: "*hello*",
});
});
it('keeps underscores', () => {
it("keeps underscores", () => {
const html = "__emphasis__";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
expect(parts.length).toBe(1);
@ -393,7 +393,7 @@ describe('editor/deserialize', function() {
text: "__emphasis__",
});
});
it('keeps square brackets', () => {
it("keeps square brackets", () => {
const html = "[not an actual link](https://example.org)";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
expect(parts.length).toBe(1);
@ -402,7 +402,7 @@ describe('editor/deserialize', function() {
text: "[not an actual link](https://example.org)",
});
});
it('escapes angle brackets', () => {
it("escapes angle brackets", () => {
const html = "> &lt;del&gt;no formatting here&lt;/del&gt;";
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
expect(parts.length).toBe(1);

View file

@ -16,126 +16,126 @@ limitations under the License.
import { diffDeletion, diffAtCaret } from "../../src/editor/diff";
describe('editor/diff', function() {
describe('diffDeletion', function() {
describe('with a single character removed', function() {
it('at start of string', function() {
describe("editor/diff", function () {
describe("diffDeletion", function () {
describe("with a single character removed", function () {
it("at start of string", function () {
const diff = diffDeletion("hello", "ello");
expect(diff.at).toBe(0);
expect(diff.removed).toBe("h");
});
it('in middle of string', function() {
it("in middle of string", function () {
const diff = diffDeletion("hello", "hllo");
expect(diff.at).toBe(1);
expect(diff.removed).toBe("e");
});
it('in middle of string with duplicate character', function() {
it("in middle of string with duplicate character", function () {
const diff = diffDeletion("hello", "helo");
expect(diff.at).toBe(3);
expect(diff.removed).toBe("l");
});
it('at end of string', function() {
it("at end of string", function () {
const diff = diffDeletion("hello", "hell");
expect(diff.at).toBe(4);
expect(diff.removed).toBe("o");
});
});
describe('with a multiple removed', function() {
it('at start of string', function() {
describe("with a multiple removed", function () {
it("at start of string", function () {
const diff = diffDeletion("hello", "llo");
expect(diff.at).toBe(0);
expect(diff.removed).toBe("he");
});
it('removing whole string', function() {
it("removing whole string", function () {
const diff = diffDeletion("hello", "");
expect(diff.at).toBe(0);
expect(diff.removed).toBe("hello");
});
it('in middle of string', function() {
it("in middle of string", function () {
const diff = diffDeletion("hello", "hlo");
expect(diff.at).toBe(1);
expect(diff.removed).toBe("el");
});
it('in middle of string with duplicate character', function() {
it("in middle of string with duplicate character", function () {
const diff = diffDeletion("hello", "heo");
expect(diff.at).toBe(2);
expect(diff.removed).toBe("ll");
});
it('at end of string', function() {
it("at end of string", function () {
const diff = diffDeletion("hello", "hel");
expect(diff.at).toBe(3);
expect(diff.removed).toBe("lo");
});
});
});
describe('diffAtCaret', function() {
it('insert at start', function() {
describe("diffAtCaret", function () {
it("insert at start", function () {
const diff = diffAtCaret("world", "hello world", 6);
expect(diff.at).toBe(0);
expect(diff.added).toBe("hello ");
expect(diff.removed).toBeFalsy();
});
it('insert at end', function() {
it("insert at end", function () {
const diff = diffAtCaret("hello", "hello world", 11);
expect(diff.at).toBe(5);
expect(diff.added).toBe(" world");
expect(diff.removed).toBeFalsy();
});
it('insert in middle', function() {
it("insert in middle", function () {
const diff = diffAtCaret("hello world", "hello cruel world", 12);
expect(diff.at).toBe(6);
expect(diff.added).toBe("cruel ");
expect(diff.removed).toBeFalsy();
});
it('replace at start', function() {
it("replace at start", function () {
const diff = diffAtCaret("morning, world!", "afternoon, world!", 9);
expect(diff.at).toBe(0);
expect(diff.removed).toBe("morning");
expect(diff.added).toBe("afternoon");
});
it('replace at end', function() {
it("replace at end", function () {
const diff = diffAtCaret("morning, world!", "morning, mars?", 14);
expect(diff.at).toBe(9);
expect(diff.removed).toBe("world!");
expect(diff.added).toBe("mars?");
});
it('replace in middle', function() {
it("replace in middle", function () {
const diff = diffAtCaret("morning, blue planet", "morning, red planet", 12);
expect(diff.at).toBe(9);
expect(diff.removed).toBe("blue");
expect(diff.added).toBe("red");
});
it('remove at start of string', function() {
it("remove at start of string", function () {
const diff = diffAtCaret("hello", "ello", 0);
expect(diff.at).toBe(0);
expect(diff.removed).toBe("h");
expect(diff.added).toBeFalsy();
});
it('removing whole string', function() {
it("removing whole string", function () {
const diff = diffAtCaret("hello", "", 0);
expect(diff.at).toBe(0);
expect(diff.removed).toBe("hello");
expect(diff.added).toBeFalsy();
});
it('remove in middle of string', function() {
it("remove in middle of string", function () {
const diff = diffAtCaret("hello", "hllo", 1);
expect(diff.at).toBe(1);
expect(diff.removed).toBe("e");
expect(diff.added).toBeFalsy();
});
it('forwards remove in middle of string', function() {
it("forwards remove in middle of string", function () {
const diff = diffAtCaret("hello", "hell", 4);
expect(diff.at).toBe(4);
expect(diff.removed).toBe("o");
expect(diff.added).toBeFalsy();
});
it('forwards remove in middle of string with duplicate character', function() {
it("forwards remove in middle of string with duplicate character", function () {
const diff = diffAtCaret("hello", "helo", 3);
expect(diff.at).toBe(3);
expect(diff.removed).toBe("l");
expect(diff.added).toBeFalsy();
});
it('remove at end of string', function() {
it("remove at end of string", function () {
const diff = diffAtCaret("hello", "hell", 4);
expect(diff.at).toBe(4);
expect(diff.removed).toBe("o");

View file

@ -18,30 +18,30 @@ import HistoryManager, { MAX_STEP_LENGTH } from "../../src/editor/history";
import EditorModel from "../../src/editor/model";
import DocumentPosition from "../../src/editor/position";
describe('editor/history', function() {
it('push, then undo', function() {
describe("editor/history", function () {
it("push, then undo", function () {
const history = new HistoryManager();
const parts = ["hello"];
const model = { serializeParts: () => parts.slice() } as unknown as EditorModel;
const caret1 = new DocumentPosition(0, 0);
const result1 = history.tryPush(model, caret1, 'insertText', {});
const result1 = history.tryPush(model, caret1, "insertText", {});
expect(result1).toEqual(true);
parts[0] = "hello world";
history.tryPush(model, new DocumentPosition(0, 0), 'insertText', {});
history.tryPush(model, new DocumentPosition(0, 0), "insertText", {});
expect(history.canUndo()).toEqual(true);
const undoState = history.undo(model);
expect(undoState.caret).toBe(caret1);
expect(undoState.parts).toEqual(["hello"]);
expect(history.canUndo()).toEqual(false);
});
it('push, undo, then redo', function() {
it("push, undo, then redo", function () {
const history = new HistoryManager();
const parts = ["hello"];
const model = { serializeParts: () => parts.slice() } as unknown as EditorModel;
history.tryPush(model, new DocumentPosition(0, 0), 'insertText', {});
history.tryPush(model, new DocumentPosition(0, 0), "insertText", {});
parts[0] = "hello world";
const caret2 = new DocumentPosition(0, 0);
history.tryPush(model, caret2, 'insertText', {});
history.tryPush(model, caret2, "insertText", {});
history.undo(model);
expect(history.canRedo()).toEqual(true);
const redoState = history.redo();
@ -50,7 +50,7 @@ describe('editor/history', function() {
expect(history.canRedo()).toEqual(false);
expect(history.canUndo()).toEqual(true);
});
it('push, undo, push, ensure you can`t redo', function() {
it("push, undo, push, ensure you can`t redo", function () {
const history = new HistoryManager();
const parts = ["hello"];
const model = { serializeParts: () => parts.slice() } as unknown as EditorModel;
@ -62,7 +62,7 @@ describe('editor/history', function() {
history.tryPush(model, new DocumentPosition(0, 0), "insertText", {});
expect(history.canRedo()).toEqual(false);
});
it('not every keystroke stores a history step', function() {
it("not every keystroke stores a history step", function () {
const history = new HistoryManager();
const parts = ["hello"];
const model = { serializeParts: () => parts.slice() } as unknown as EditorModel;
@ -80,7 +80,7 @@ describe('editor/history', function() {
expect(history.canUndo()).toEqual(false);
expect(keystrokeCount).toEqual(MAX_STEP_LENGTH + 1); // +1 before we type before checking
});
it('history step is added at word boundary', function() {
it("history step is added at word boundary", function () {
const history = new HistoryManager();
const model = { serializeParts: () => parts.slice() } as unknown as EditorModel;
const parts = ["h"];
@ -108,7 +108,7 @@ describe('editor/history', function() {
expect(undoResult.caret).toEqual(spaceCaret);
expect(undoResult.parts).toEqual(["hi "]);
});
it('keystroke that didn\'t add a step can undo', function() {
it("keystroke that didn't add a step can undo", function () {
const history = new HistoryManager();
const parts = ["hello"];
const model = { serializeParts: () => parts.slice() } as unknown as EditorModel;
@ -122,7 +122,7 @@ describe('editor/history', function() {
expect(undoState.caret).toEqual(firstCaret);
expect(undoState.parts).toEqual(["hello"]);
});
it('undo after keystroke that didn\'t add a step is able to redo', function() {
it("undo after keystroke that didn't add a step is able to redo", function () {
const history = new HistoryManager();
const parts = ["hello"];
const model = { serializeParts: () => parts.slice() } as unknown as EditorModel;
@ -137,12 +137,12 @@ describe('editor/history', function() {
expect(redoState.parts).toEqual(["helloo"]);
});
it('overwriting text always stores a step', function() {
it("overwriting text always stores a step", function () {
const history = new HistoryManager();
const parts = ["hello"];
const model = { serializeParts: () => parts.slice() } as unknown as EditorModel;
const firstCaret = new DocumentPosition(0, 0);
history.tryPush(model, firstCaret, 'insertText', {});
history.tryPush(model, firstCaret, "insertText", {});
const diff = { at: 1, added: "a", removed: "e" };
const secondCaret = new DocumentPosition(1, 1);
const result = history.tryPush(model, secondCaret, "insertText", diff);

View file

@ -38,7 +38,7 @@ class MockAutoComplete {
}
tryComplete(close = true) {
const matches = this._completions.filter(o => {
const matches = this._completions.filter((o) => {
return o.resourceId.startsWith(this._part.text);
});
if (matches.length === 1 && this._part.text.length > 1) {
@ -62,7 +62,9 @@ class MockAutoComplete {
// MockClient & MockRoom are only used for avatars in room and user pills,
// which is not tested
class MockRoom {
getMember() { return null; }
getMember() {
return null;
}
}
export function createPartCreator(completions = []) {

View file

@ -18,9 +18,9 @@ import EditorModel from "../../src/editor/model";
import { createPartCreator, createRenderer } from "./mock";
import DocumentOffset from "../../src/editor/offset";
describe('editor/model', function() {
describe('plain text manipulation', function() {
it('insert text into empty document', function() {
describe("editor/model", function () {
describe("plain text manipulation", function () {
it("insert text into empty document", function () {
const renderer = createRenderer();
const model = new EditorModel([], createPartCreator(), renderer);
model.update("hello", "insertText", new DocumentOffset(5, true));
@ -31,7 +31,7 @@ describe('editor/model', function() {
expect(model.parts[0].type).toBe("plain");
expect(model.parts[0].text).toBe("hello");
});
it('append text to existing document', function() {
it("append text to existing document", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello")], pc, renderer);
@ -43,7 +43,7 @@ describe('editor/model', function() {
expect(model.parts[0].type).toBe("plain");
expect(model.parts[0].text).toBe("hello world");
});
it('prepend text to existing document', function() {
it("prepend text to existing document", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([pc.plain("world")], pc, renderer);
@ -56,8 +56,8 @@ describe('editor/model', function() {
expect(model.parts[0].text).toBe("hello world");
});
});
describe('handling line breaks', function() {
it('insert new line into existing document', function() {
describe("handling line breaks", function () {
it("insert new line into existing document", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello")], pc, renderer);
@ -71,7 +71,7 @@ describe('editor/model', function() {
expect(model.parts[1].type).toBe("newline");
expect(model.parts[1].text).toBe("\n");
});
it('insert multiple new lines into existing document', function() {
it("insert multiple new lines into existing document", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello")], pc, renderer);
@ -91,15 +91,14 @@ describe('editor/model', function() {
expect(model.parts[4].type).toBe("plain");
expect(model.parts[4].text).toBe("world!");
});
it('type in empty line', function() {
it("type in empty line", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello"),
pc.newline(),
pc.newline(),
pc.plain("world"),
], pc, renderer);
const model = new EditorModel(
[pc.plain("hello"), pc.newline(), pc.newline(), pc.plain("world")],
pc,
renderer,
);
model.update("hello\nwarm\nworld", "insertText", new DocumentOffset(10, true));
expect(renderer.count).toBe(1);
expect(renderer.caret.index).toBe(2);
@ -117,14 +116,11 @@ describe('editor/model', function() {
expect(model.parts[4].text).toBe("world");
});
});
describe('non-editable part manipulation', function() {
it('typing at start of non-editable part prepends', function() {
describe("non-editable part manipulation", function () {
it("typing at start of non-editable part prepends", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("try "),
pc.roomPill("#someroom"),
], pc, renderer);
const model = new EditorModel([pc.plain("try "), pc.roomPill("#someroom")], pc, renderer);
model.update("try foo#someroom", "insertText", new DocumentOffset(7, false));
expect(renderer.caret.index).toBe(0);
expect(renderer.caret.offset).toBe(7);
@ -134,14 +130,10 @@ describe('editor/model', function() {
expect(model.parts[1].type).toBe("room-pill");
expect(model.parts[1].text).toBe("#someroom");
});
it('typing in middle of non-editable part appends', function() {
it("typing in middle of non-editable part appends", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("try "),
pc.roomPill("#someroom"),
pc.plain("?"),
], pc, renderer);
const model = new EditorModel([pc.plain("try "), pc.roomPill("#someroom"), pc.plain("?")], pc, renderer);
model.update("try #some perhapsroom?", "insertText", new DocumentOffset(17, false));
expect(renderer.caret.index).toBe(2);
expect(renderer.caret.offset).toBe(8);
@ -153,7 +145,7 @@ describe('editor/model', function() {
expect(model.parts[2].type).toBe("plain");
expect(model.parts[2].text).toBe(" perhaps?");
});
it('remove non-editable part with backspace', function() {
it("remove non-editable part with backspace", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([pc.roomPill("#someroom")], pc, renderer);
@ -163,7 +155,7 @@ describe('editor/model', function() {
expect(renderer.caret.offset).toBe(0);
expect(model.parts.length).toBe(0);
});
it('remove non-editable part with delete', function() {
it("remove non-editable part with delete", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([pc.roomPill("#someroom")], pc, renderer);
@ -174,8 +166,8 @@ describe('editor/model', function() {
expect(model.parts.length).toBe(0);
});
});
describe('auto-complete', function() {
it('insert user pill', function() {
describe("auto-complete", function () {
it("insert user pill", function () {
const renderer = createRenderer();
const pc = createPartCreator([{ resourceId: "@alice", label: "Alice" }]);
const model = new EditorModel([pc.plain("hello ")], pc, renderer);
@ -205,7 +197,7 @@ describe('editor/model', function() {
expect(model.parts[1].text).toBe("Alice");
});
it('insert room pill', function() {
it("insert room pill", function () {
const renderer = createRenderer();
const pc = createPartCreator([{ resourceId: "#riot-dev" }]);
const model = new EditorModel([pc.plain("hello ")], pc, renderer);
@ -235,7 +227,7 @@ describe('editor/model', function() {
expect(model.parts[1].text).toBe("#riot-dev");
});
it('type after inserting pill', function() {
it("type after inserting pill", function () {
const renderer = createRenderer();
const pc = createPartCreator([{ resourceId: "#riot-dev" }]);
const model = new EditorModel([pc.plain("hello ")], pc, renderer);
@ -258,7 +250,7 @@ describe('editor/model', function() {
expect(model.parts[2].text).toBe("!!");
});
it('pasting text does not trigger auto-complete', function() {
it("pasting text does not trigger auto-complete", function () {
const renderer = createRenderer();
const pc = createPartCreator([{ resourceId: "#define-room" }]);
const model = new EditorModel([pc.plain("try ")], pc, renderer);
@ -273,7 +265,7 @@ describe('editor/model', function() {
expect(model.parts[0].text).toBe("try #define");
});
it('dropping text does not trigger auto-complete', function() {
it("dropping text does not trigger auto-complete", function () {
const renderer = createRenderer();
const pc = createPartCreator([{ resourceId: "#define-room" }]);
const model = new EditorModel([pc.plain("try ")], pc, renderer);
@ -288,7 +280,7 @@ describe('editor/model', function() {
expect(model.parts[0].text).toBe("try #define");
});
it('insert room pill without splitting at the colon', () => {
it("insert room pill without splitting at the colon", () => {
const renderer = createRenderer();
const pc = createPartCreator([{ resourceId: "#room:server" }]);
const model = new EditorModel([], pc, renderer);
@ -308,7 +300,7 @@ describe('editor/model', function() {
expect(model.parts[0].text).toBe("#room:s");
});
it('allow typing e-mail addresses without splitting at the @', () => {
it("allow typing e-mail addresses without splitting at the @", () => {
const renderer = createRenderer();
const pc = createPartCreator([{ resourceId: "@alice", label: "Alice" }]);
const model = new EditorModel([], pc, renderer);

View file

@ -24,54 +24,48 @@ import {
toggleInlineFormat,
} from "../../src/editor/operations";
import { Formatting } from "../../src/components/views/rooms/MessageComposerFormatBar";
import { longestBacktickSequence } from '../../src/editor/deserialize';
import { longestBacktickSequence } from "../../src/editor/deserialize";
const SERIALIZED_NEWLINE = { "text": "\n", "type": "newline" };
const SERIALIZED_NEWLINE = { text: "\n", type: "newline" };
describe("editor/operations: formatting operations", () => {
const renderer = createRenderer();
const pc = createPartCreator();
describe("formatRange", () => {
it.each([
[Formatting.Bold, "hello **world**!"],
])("should correctly wrap format %s", (formatting: Formatting, expected: string) => {
const model = new EditorModel([
pc.plain("hello world!"),
], pc, renderer);
it.each([[Formatting.Bold, "hello **world**!"]])(
"should correctly wrap format %s",
(formatting: Formatting, expected: string) => {
const model = new EditorModel([pc.plain("hello world!")], pc, renderer);
const range = model.startRange(model.positionForOffset(6, false),
model.positionForOffset(11, false)); // around "world"
const range = model.startRange(model.positionForOffset(6, false), model.positionForOffset(11, false)); // around "world"
expect(range.parts[0].text).toBe("world");
expect(model.serializeParts()).toEqual([{ "text": "hello world!", "type": "plain" }]);
formatRange(range, formatting);
expect(model.serializeParts()).toEqual([{ "text": expected, "type": "plain" }]);
});
expect(range.parts[0].text).toBe("world");
expect(model.serializeParts()).toEqual([{ text: "hello world!", type: "plain" }]);
formatRange(range, formatting);
expect(model.serializeParts()).toEqual([{ text: expected, type: "plain" }]);
},
);
it("should apply to word range is within if length 0", () => {
const model = new EditorModel([
pc.plain("hello world!"),
], pc, renderer);
const model = new EditorModel([pc.plain("hello world!")], pc, renderer);
const range = model.startRange(model.positionForOffset(6, false));
expect(model.serializeParts()).toEqual([{ "text": "hello world!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello world!", type: "plain" }]);
formatRange(range, Formatting.Bold);
expect(model.serializeParts()).toEqual([{ "text": "hello **world!**", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello **world!**", type: "plain" }]);
});
it("should do nothing for a range with length 0 at initialisation", () => {
const model = new EditorModel([
pc.plain("hello world!"),
], pc, renderer);
const model = new EditorModel([pc.plain("hello world!")], pc, renderer);
const range = model.startRange(model.positionForOffset(6, false));
range.setWasEmpty(false);
expect(model.serializeParts()).toEqual([{ "text": "hello world!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello world!", type: "plain" }]);
formatRange(range, Formatting.Bold);
expect(model.serializeParts()).toEqual([{ "text": "hello world!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello world!", type: "plain" }]);
});
});
@ -83,12 +77,12 @@ describe("editor/operations: formatting operations", () => {
["[testing]()", "testing|", ""],
["[testing](foobar)", "testing|", ""],
])("converts %s -> %s", (input: string, expectation: string, text: string) => {
const model = new EditorModel([
pc.plain(`foo ${input} bar`),
], pc, renderer);
const model = new EditorModel([pc.plain(`foo ${input} bar`)], pc, renderer);
const range = model.startRange(model.positionForOffset(4, false),
model.positionForOffset(4 + input.length, false)); // around input
const range = model.startRange(
model.positionForOffset(4, false),
model.positionForOffset(4 + input.length, false),
); // around input
expect(range.parts[0].text).toBe(input);
formatRangeAsLink(range, text);
@ -99,192 +93,173 @@ describe("editor/operations: formatting operations", () => {
describe("toggleInlineFormat", () => {
it("works for words", () => {
const model = new EditorModel([
pc.plain("hello world!"),
], pc, renderer);
const model = new EditorModel([pc.plain("hello world!")], pc, renderer);
const range = model.startRange(model.positionForOffset(6, false),
model.positionForOffset(11, false)); // around "world"
const range = model.startRange(model.positionForOffset(6, false), model.positionForOffset(11, false)); // around "world"
expect(range.parts[0].text).toBe("world");
expect(model.serializeParts()).toEqual([{ "text": "hello world!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello world!", type: "plain" }]);
formatRange(range, Formatting.Italics);
expect(model.serializeParts()).toEqual([{ "text": "hello _world_!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello _world_!", type: "plain" }]);
});
describe('escape backticks', () => {
it('works for escaping backticks in between texts', () => {
describe("escape backticks", () => {
it("works for escaping backticks in between texts", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello ` world!"),
], pc, renderer);
const model = new EditorModel([pc.plain("hello ` world!")], pc, renderer);
const range = model.startRange(model.positionForOffset(0, false),
model.positionForOffset(13, false)); // hello ` world
const range = model.startRange(model.positionForOffset(0, false), model.positionForOffset(13, false)); // hello ` world
expect(range.parts[0].text.trim().includes("`")).toBeTruthy();
expect(longestBacktickSequence(range.parts[0].text.trim())).toBe(1);
expect(model.serializeParts()).toEqual([{ "text": "hello ` world!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello ` world!", type: "plain" }]);
formatRangeAsCode(range);
expect(model.serializeParts()).toEqual([{ "text": "``hello ` world``!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "``hello ` world``!", type: "plain" }]);
});
it('escapes longer backticks in between text', () => {
it("escapes longer backticks in between text", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello```world"),
], pc, renderer);
const model = new EditorModel([pc.plain("hello```world")], pc, renderer);
const range = model.startRange(model.positionForOffset(0, false),
model.getPositionAtEnd()); // hello```world
const range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd()); // hello```world
expect(range.parts[0].text.includes("`")).toBeTruthy();
expect(longestBacktickSequence(range.parts[0].text)).toBe(3);
expect(model.serializeParts()).toEqual([{ "text": "hello```world", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello```world", type: "plain" }]);
formatRangeAsCode(range);
expect(model.serializeParts()).toEqual([{ "text": "````hello```world````", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "````hello```world````", type: "plain" }]);
});
it('escapes non-consecutive with varying length backticks in between text', () => {
it("escapes non-consecutive with varying length backticks in between text", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hell```o`w`o``rld"),
], pc, renderer);
const model = new EditorModel([pc.plain("hell```o`w`o``rld")], pc, renderer);
const range = model.startRange(model.positionForOffset(0, false),
model.getPositionAtEnd()); // hell```o`w`o``rld
const range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd()); // hell```o`w`o``rld
expect(range.parts[0].text.includes("`")).toBeTruthy();
expect(longestBacktickSequence(range.parts[0].text)).toBe(3);
expect(model.serializeParts()).toEqual([{ "text": "hell```o`w`o``rld", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hell```o`w`o``rld", type: "plain" }]);
formatRangeAsCode(range);
expect(model.serializeParts()).toEqual([{ "text": "````hell```o`w`o``rld````", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "````hell```o`w`o``rld````", type: "plain" }]);
});
it('untoggles correctly if its already formatted', () => {
it("untoggles correctly if its already formatted", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("```hello``world```"),
], pc, renderer);
const model = new EditorModel([pc.plain("```hello``world```")], pc, renderer);
const range = model.startRange(model.positionForOffset(0, false),
model.getPositionAtEnd()); // hello``world
const range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd()); // hello``world
expect(range.parts[0].text.includes("`")).toBeTruthy();
expect(longestBacktickSequence(range.parts[0].text)).toBe(3);
expect(model.serializeParts()).toEqual([{ "text": "```hello``world```", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "```hello``world```", type: "plain" }]);
formatRangeAsCode(range);
expect(model.serializeParts()).toEqual([{ "text": "hello``world", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello``world", type: "plain" }]);
});
it('untoggles correctly it contains varying length of backticks between text', () => {
it("untoggles correctly it contains varying length of backticks between text", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("````hell```o`w`o``rld````"),
], pc, renderer);
const model = new EditorModel([pc.plain("````hell```o`w`o``rld````")], pc, renderer);
const range = model.startRange(model.positionForOffset(0, false),
model.getPositionAtEnd()); // hell```o`w`o``rld
const range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd()); // hell```o`w`o``rld
expect(range.parts[0].text.includes("`")).toBeTruthy();
expect(longestBacktickSequence(range.parts[0].text)).toBe(4);
expect(model.serializeParts()).toEqual([{ "text": "````hell```o`w`o``rld````", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "````hell```o`w`o``rld````", type: "plain" }]);
formatRangeAsCode(range);
expect(model.serializeParts()).toEqual([{ "text": "hell```o`w`o``rld", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hell```o`w`o``rld", type: "plain" }]);
});
});
it('works for parts of words', () => {
it("works for parts of words", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello world!"),
], pc, renderer);
const model = new EditorModel([pc.plain("hello world!")], pc, renderer);
const range = model.startRange(model.positionForOffset(7, false),
model.positionForOffset(10, false)); // around "orl"
const range = model.startRange(model.positionForOffset(7, false), model.positionForOffset(10, false)); // around "orl"
expect(range.parts[0].text).toBe("orl");
expect(model.serializeParts()).toEqual([{ "text": "hello world!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello world!", type: "plain" }]);
toggleInlineFormat(range, "*");
expect(model.serializeParts()).toEqual([{ "text": "hello w*orl*d!", "type": "plain" }]);
expect(model.serializeParts()).toEqual([{ text: "hello w*orl*d!", type: "plain" }]);
});
it('works for around pills', () => {
it("works for around pills", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello there "),
pc.atRoomPill("@room"),
pc.plain(", how are you doing?"),
], pc, renderer);
const model = new EditorModel(
[pc.plain("hello there "), pc.atRoomPill("@room"), pc.plain(", how are you doing?")],
pc,
renderer,
);
const range = model.startRange(model.positionForOffset(6, false),
model.positionForOffset(30, false)); // around "there @room, how are you"
const range = model.startRange(model.positionForOffset(6, false), model.positionForOffset(30, false)); // around "there @room, how are you"
expect(range.parts.map(p => p.text).join("")).toBe("there @room, how are you");
expect(range.parts.map((p) => p.text).join("")).toBe("there @room, how are you");
expect(model.serializeParts()).toEqual([
{ "text": "hello there ", "type": "plain" },
{ "text": "@room", "type": "at-room-pill" },
{ "text": ", how are you doing?", "type": "plain" },
{ text: "hello there ", type: "plain" },
{ text: "@room", type: "at-room-pill" },
{ text: ", how are you doing?", type: "plain" },
]);
formatRange(range, Formatting.Italics);
expect(model.serializeParts()).toEqual([
{ "text": "hello _there ", "type": "plain" },
{ "text": "@room", "type": "at-room-pill" },
{ "text": ", how are you_ doing?", "type": "plain" },
{ text: "hello _there ", type: "plain" },
{ text: "@room", type: "at-room-pill" },
{ text: ", how are you_ doing?", type: "plain" },
]);
});
it('works for a paragraph', () => {
it("works for a paragraph", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello world,"),
pc.newline(),
pc.plain("how are you doing?"),
], pc, renderer);
const model = new EditorModel(
[pc.plain("hello world,"), pc.newline(), pc.plain("how are you doing?")],
pc,
renderer,
);
const range = model.startRange(model.positionForOffset(6, false),
model.positionForOffset(16, false)); // around "world,\nhow"
const range = model.startRange(model.positionForOffset(6, false), model.positionForOffset(16, false)); // around "world,\nhow"
expect(range.parts.map(p => p.text).join("")).toBe("world,\nhow");
expect(range.parts.map((p) => p.text).join("")).toBe("world,\nhow");
expect(model.serializeParts()).toEqual([
{ "text": "hello world,", "type": "plain" },
{ text: "hello world,", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "how are you doing?", "type": "plain" },
{ text: "how are you doing?", type: "plain" },
]);
formatRange(range, Formatting.Bold);
expect(model.serializeParts()).toEqual([
{ "text": "hello **world,", "type": "plain" },
{ text: "hello **world,", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "how** are you doing?", "type": "plain" },
{ text: "how** are you doing?", type: "plain" },
]);
});
it('works for a paragraph with spurious breaks around it in selected range', () => {
it("works for a paragraph with spurious breaks around it in selected range", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.newline(),
pc.newline(),
pc.plain("hello world,"),
pc.newline(),
pc.plain("how are you doing?"),
pc.newline(),
pc.newline(),
], pc, renderer);
const model = new EditorModel(
[
pc.newline(),
pc.newline(),
pc.plain("hello world,"),
pc.newline(),
pc.plain("how are you doing?"),
pc.newline(),
pc.newline(),
],
pc,
renderer,
);
const range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd()); // select-all
const range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd()); // select-all
expect(range.parts.map(p => p.text).join("")).toBe("\n\nhello world,\nhow are you doing?\n\n");
expect(range.parts.map((p) => p.text).join("")).toBe("\n\nhello world,\nhow are you doing?\n\n");
expect(model.serializeParts()).toEqual([
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": "hello world,", "type": "plain" },
{ text: "hello world,", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "how are you doing?", "type": "plain" },
{ text: "how are you doing?", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
]);
@ -292,64 +267,65 @@ describe("editor/operations: formatting operations", () => {
expect(model.serializeParts()).toEqual([
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": "**hello world,", "type": "plain" },
{ text: "**hello world,", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "how are you doing?**", "type": "plain" },
{ text: "how are you doing?**", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
]);
});
it('works for multiple paragraph', () => {
it("works for multiple paragraph", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello world,"),
pc.newline(),
pc.plain("how are you doing?"),
pc.newline(),
pc.newline(),
pc.plain("new paragraph"),
], pc, renderer);
const model = new EditorModel(
[
pc.plain("hello world,"),
pc.newline(),
pc.plain("how are you doing?"),
pc.newline(),
pc.newline(),
pc.plain("new paragraph"),
],
pc,
renderer,
);
let range = model.startRange(model.positionForOffset(0, true), model.getPositionAtEnd()); // select-all
expect(model.serializeParts()).toEqual([
{ "text": "hello world,", "type": "plain" },
{ text: "hello world,", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "how are you doing?", "type": "plain" },
{ text: "how are you doing?", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": "new paragraph", "type": "plain" },
{ text: "new paragraph", type: "plain" },
]);
toggleInlineFormat(range, "__");
expect(model.serializeParts()).toEqual([
{ "text": "__hello world,", "type": "plain" },
{ text: "__hello world,", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "how are you doing?__", "type": "plain" },
{ text: "how are you doing?__", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": "__new paragraph__", "type": "plain" },
{ text: "__new paragraph__", type: "plain" },
]);
range = model.startRange(model.positionForOffset(0, true), model.getPositionAtEnd()); // select-all
toggleInlineFormat(range, "__");
expect(model.serializeParts()).toEqual([
{ "text": "hello world,", "type": "plain" },
{ text: "hello world,", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "how are you doing?", "type": "plain" },
{ text: "how are you doing?", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": "new paragraph", "type": "plain" },
{ text: "new paragraph", type: "plain" },
]);
});
it('format word at caret position at beginning of new line without previous selection', () => {
it("format word at caret position at beginning of new line without previous selection", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.newline(),
pc.plain("hello!"),
], pc, renderer);
const model = new EditorModel([pc.newline(), pc.plain("hello!")], pc, renderer);
let range = model.startRange(model.positionForOffset(1, false));
@ -359,17 +335,11 @@ describe("editor/operations: formatting operations", () => {
formatRange(range, Formatting.Bold); // Toggle
expect(model.serializeParts()).toEqual([
SERIALIZED_NEWLINE,
{ "text": "**hello!**", "type": "plain" },
]);
expect(model.serializeParts()).toEqual([SERIALIZED_NEWLINE, { text: "**hello!**", type: "plain" }]);
formatRange(range, Formatting.Bold); // Untoggle
expect(model.serializeParts()).toEqual([
SERIALIZED_NEWLINE,
{ "text": "hello!", "type": "plain" },
]);
expect(model.serializeParts()).toEqual([SERIALIZED_NEWLINE, { text: "hello!", type: "plain" }]);
// Check if it also works for code as it uses toggleInlineFormatting only indirectly
range = model.startRange(model.positionForOffset(1, false));
@ -377,31 +347,25 @@ describe("editor/operations: formatting operations", () => {
formatRange(range, Formatting.Code); // Toggle
expect(model.serializeParts()).toEqual([
SERIALIZED_NEWLINE,
{ "text": "`hello!`", "type": "plain" },
]);
expect(model.serializeParts()).toEqual([SERIALIZED_NEWLINE, { text: "`hello!`", type: "plain" }]);
formatRange(range, Formatting.Code); // Untoggle
expect(model.serializeParts()).toEqual([
SERIALIZED_NEWLINE,
{ "text": "hello!", "type": "plain" },
]);
expect(model.serializeParts()).toEqual([SERIALIZED_NEWLINE, { text: "hello!", type: "plain" }]);
});
it('caret resets correctly to current line when untoggling formatting while caret at line end', () => {
it("caret resets correctly to current line when untoggling formatting while caret at line end", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello **hello!**"),
pc.newline(),
pc.plain("world"),
], pc, renderer);
const model = new EditorModel(
[pc.plain("hello **hello!**"), pc.newline(), pc.plain("world")],
pc,
renderer,
);
expect(model.serializeParts()).toEqual([
{ "text": "hello **hello!**", "type": "plain" },
{ text: "hello **hello!**", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "world", "type": "plain" },
{ text: "world", type: "plain" },
]);
const endOfFirstLine = 16;
@ -412,121 +376,118 @@ describe("editor/operations: formatting operations", () => {
// We expect formatting to still happen in the first line as the caret should not jump down
expect(model.serializeParts()).toEqual([
{ "text": "hello _hello!_", "type": "plain" },
{ text: "hello _hello!_", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "world", "type": "plain" },
{ text: "world", type: "plain" },
]);
});
it('format link in front of new line part', () => {
it("format link in front of new line part", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello!"),
pc.newline(),
pc.plain("world!"),
pc.newline(),
], pc, renderer);
const model = new EditorModel(
[pc.plain("hello!"), pc.newline(), pc.plain("world!"), pc.newline()],
pc,
renderer,
);
let range = model.startRange(model.getPositionAtEnd().asOffset(model).add(-1).asPosition(model)); // select-all
expect(model.serializeParts()).toEqual([
{ "text": "hello!", "type": "plain" },
{ text: "hello!", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "world!", "type": "plain" },
{ text: "world!", type: "plain" },
SERIALIZED_NEWLINE,
]);
formatRange(range, Formatting.InsertLink); // Toggle
expect(model.serializeParts()).toEqual([
{ "text": "hello!", "type": "plain" },
{ text: "hello!", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "[world!]()", "type": "plain" },
{ text: "[world!]()", type: "plain" },
SERIALIZED_NEWLINE,
]);
range = model.startRange(model.getPositionAtEnd().asOffset(model).add(-1).asPosition(model)); // select-all
formatRange(range, Formatting.InsertLink); // Untoggle
expect(model.serializeParts()).toEqual([
{ "text": "hello!", "type": "plain" },
{ text: "hello!", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "world!", "type": "plain" },
{ text: "world!", type: "plain" },
SERIALIZED_NEWLINE,
]);
});
it('format multi line code', () => {
it("format multi line code", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("int x = 1;"),
pc.newline(),
pc.newline(),
pc.plain("int y = 42;"),
], pc, renderer);
const model = new EditorModel(
[pc.plain("int x = 1;"), pc.newline(), pc.newline(), pc.plain("int y = 42;")],
pc,
renderer,
);
let range = model.startRange(model.positionForOffset(0), model.getPositionAtEnd()); // select-all
expect(range.parts.map(p => p.text).join("")).toBe("int x = 1;\n\nint y = 42;");
expect(range.parts.map((p) => p.text).join("")).toBe("int x = 1;\n\nint y = 42;");
expect(model.serializeParts()).toEqual([
{ "text": "int x = 1;", "type": "plain" },
{ text: "int x = 1;", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": "int y = 42;", "type": "plain" },
{ text: "int y = 42;", type: "plain" },
]);
formatRange(range, Formatting.Code); // Toggle
expect(model.serializeParts()).toEqual([
{ "text": "```", "type": "plain" },
{ text: "```", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "int x = 1;", "type": "plain" },
{ text: "int x = 1;", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": "int y = 42;", "type": "plain" },
{ text: "int y = 42;", type: "plain" },
SERIALIZED_NEWLINE,
{ "text": "```", "type": "plain" },
{ text: "```", type: "plain" },
]);
range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd()); // select-all
formatRange(range, Formatting.Code); // Untoggle
expect(model.serializeParts()).toEqual([
{ "text": "int x = 1;", "type": "plain" },
{ text: "int x = 1;", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": "int y = 42;", "type": "plain" },
{ text: "int y = 42;", type: "plain" },
]);
});
it('does not format pure white space', () => {
it("does not format pure white space", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain(" "),
pc.newline(),
pc.newline(),
pc.plain(" "),
], pc, renderer);
const model = new EditorModel(
[pc.plain(" "), pc.newline(), pc.newline(), pc.plain(" ")],
pc,
renderer,
);
const range = model.startRange(model.positionForOffset(0), model.getPositionAtEnd()); // select-all
expect(range.parts.map(p => p.text).join("")).toBe(" \n\n ");
expect(range.parts.map((p) => p.text).join("")).toBe(" \n\n ");
expect(model.serializeParts()).toEqual([
{ "text": " ", "type": "plain" },
{ text: " ", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": " ", "type": "plain" },
{ text: " ", type: "plain" },
]);
formatRange(range, Formatting.Bold);
expect(model.serializeParts()).toEqual([
{ "text": " ", "type": "plain" },
{ text: " ", type: "plain" },
SERIALIZED_NEWLINE,
SERIALIZED_NEWLINE,
{ "text": " ", "type": "plain" },
{ text: " ", type: "plain" },
]);
});
});

View file

@ -27,52 +27,64 @@ function createRenderer() {
return render;
}
describe('editor/position', function() {
it('move first position backward in empty model', function() {
describe("editor/position", function () {
it("move first position backward in empty model", function () {
const model = new EditorModel([], createPartCreator(), createRenderer());
const pos = model.positionForOffset(0, true);
const pos2 = pos.backwardsWhile(model, () => true);
expect(pos).toBe(pos2);
});
it('move first position forwards in empty model', function() {
it("move first position forwards in empty model", function () {
const model = new EditorModel([], createPartCreator(), createRenderer());
const pos = model.positionForOffset(0, true);
const pos2 = pos.forwardsWhile(model, () => true);
expect(pos).toBe(pos2);
});
it('move forwards within one part', function() {
it("move forwards within one part", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello")], pc, createRenderer());
const pos = model.positionForOffset(1);
let n = 3;
const pos2 = pos.forwardsWhile(model, () => { n -= 1; return n >= 0; });
const pos2 = pos.forwardsWhile(model, () => {
n -= 1;
return n >= 0;
});
expect(pos2.index).toBe(0);
expect(pos2.offset).toBe(4);
});
it('move forwards crossing to other part', function() {
it("move forwards crossing to other part", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello"), pc.plain(" world")], pc, createRenderer());
const pos = model.positionForOffset(4);
let n = 3;
const pos2 = pos.forwardsWhile(model, () => { n -= 1; return n >= 0; });
const pos2 = pos.forwardsWhile(model, () => {
n -= 1;
return n >= 0;
});
expect(pos2.index).toBe(1);
expect(pos2.offset).toBe(2);
});
it('move backwards within one part', function() {
it("move backwards within one part", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello")], pc, createRenderer());
const pos = model.positionForOffset(4);
let n = 3;
const pos2 = pos.backwardsWhile(model, () => { n -= 1; return n >= 0; });
const pos2 = pos.backwardsWhile(model, () => {
n -= 1;
return n >= 0;
});
expect(pos2.index).toBe(0);
expect(pos2.offset).toBe(1);
});
it('move backwards crossing to other part', function() {
it("move backwards crossing to other part", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello"), pc.plain(" world")], pc, createRenderer());
const pos = model.positionForOffset(7);
let n = 3;
const pos2 = pos.backwardsWhile(model, () => { n -= 1; return n >= 0; });
const pos2 = pos.backwardsWhile(model, () => {
n -= 1;
return n >= 0;
});
expect(pos2.index).toBe(0);
expect(pos2.offset).toBe(4);
});

View file

@ -19,25 +19,25 @@ import { createPartCreator, createRenderer } from "./mock";
const pillChannel = "#riot-dev:matrix.org";
describe('editor/range', function() {
it('range on empty model', function() {
describe("editor/range", function () {
it("range on empty model", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([], pc, renderer);
const range = model.startRange(model.positionForOffset(0, true)); // after "world"
const range = model.startRange(model.positionForOffset(0, true)); // after "world"
let called = false;
range.expandBackwardsWhile(chr => {
range.expandBackwardsWhile((chr) => {
called = true;
return true;
});
expect(called).toBe(false);
expect(range.text).toBe("");
});
it('range replace within a part', function() {
it("range replace within a part", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello world!!!!")], pc, renderer);
const range = model.startRange(model.positionForOffset(11)); // after "world"
const range = model.startRange(model.positionForOffset(11)); // after "world"
range.expandBackwardsWhile((index, offset) => model.parts[index].text[offset] !== " ");
expect(range.text).toBe("world");
range.replace([pc.roomPill(pillChannel)]);
@ -49,16 +49,15 @@ describe('editor/range', function() {
expect(model.parts[2].text).toBe("!!!!");
expect(model.parts.length).toBe(3);
});
it('range replace across parts', function() {
it("range replace across parts", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("try to re"),
pc.plain("pla"),
pc.plain("ce "),
pc.plain("me"),
], pc, renderer);
const range = model.startRange(model.positionForOffset(14)); // after "replace"
const model = new EditorModel(
[pc.plain("try to re"), pc.plain("pla"), pc.plain("ce "), pc.plain("me")],
pc,
renderer,
);
const range = model.startRange(model.positionForOffset(14)); // after "replace"
range.expandBackwardsWhile((index, offset) => model.parts[index].text[offset] !== " ");
expect(range.text).toBe("replace");
range.replace([pc.roomPill(pillChannel)]);
@ -71,14 +70,11 @@ describe('editor/range', function() {
expect(model.parts.length).toBe(3);
});
// bug found while implementing tab completion
it('replace a part with an identical part with start position at end of previous part', function() {
it("replace a part with an identical part with start position at end of previous part", function () {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("hello "),
pc.pillCandidate("man"),
], pc, renderer);
const range = model.startRange(model.positionForOffset(9, true)); // before "man"
const model = new EditorModel([pc.plain("hello "), pc.pillCandidate("man")], pc, renderer);
const range = model.startRange(model.positionForOffset(9, true)); // before "man"
range.expandBackwardsWhile((index, offset) => model.parts[index].text[offset] !== " ");
expect(range.text).toBe("man");
range.replace([pc.pillCandidate(range.text)]);
@ -88,12 +84,10 @@ describe('editor/range', function() {
expect(model.parts[1].text).toBe("man");
expect(model.parts.length).toBe(2);
});
it('range trim spaces off both ends', () => {
it("range trim spaces off both ends", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const model = new EditorModel([
pc.plain("abc abc abc"),
], pc, renderer);
const model = new EditorModel([pc.plain("abc abc abc")], pc, renderer);
const range = model.startRange(
model.positionForOffset(3, false), // at end of first `abc`
model.positionForOffset(8, false), // at start of last `abc`
@ -104,17 +98,12 @@ describe('editor/range', function() {
expect(range.parts[0].text).toBe("abc");
});
// test for edge case when the selection just consists of whitespace
it('range trim just whitespace', () => {
it("range trim just whitespace", () => {
const renderer = createRenderer();
const pc = createPartCreator();
const whitespace = " \n \n\n";
const model = new EditorModel([
pc.plain(whitespace),
], pc, renderer);
const range = model.startRange(
model.positionForOffset(0, false),
model.getPositionAtEnd(),
);
const model = new EditorModel([pc.plain(whitespace)], pc, renderer);
const range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd());
expect(range.text).toBe(whitespace);
range.trim();

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from 'matrix-js-sdk/src/matrix';
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { parseEvent } from "../../src/editor/deserialize";
import EditorModel from '../../src/editor/model';
import DocumentOffset from '../../src/editor/offset';
import { htmlSerializeIfNeeded, textSerialize } from '../../src/editor/serialize';
import EditorModel from "../../src/editor/model";
import DocumentOffset from "../../src/editor/offset";
import { htmlSerializeIfNeeded, textSerialize } from "../../src/editor/serialize";
import { createPartCreator } from "./mock";
function htmlMessage(formattedBody: string, msgtype = "m.text") {
@ -37,11 +37,7 @@ function htmlMessage(formattedBody: string, msgtype = "m.text") {
async function md2html(markdown: string): Promise<string> {
const pc = createPartCreator();
const oldModel = new EditorModel([], pc, () => {});
await oldModel.update(
markdown,
"insertText",
new DocumentOffset(markdown.length, false),
);
await oldModel.update(markdown, "insertText", new DocumentOffset(markdown.length, false));
return htmlSerializeIfNeeded(oldModel, { forceHTML: true });
}
@ -60,8 +56,8 @@ async function roundTripHtml(html: string): Promise<string> {
return await md2html(html2md(html));
}
describe('editor/roundtrip', function() {
describe('markdown messages should round-trip if they contain', function() {
describe("editor/roundtrip", function () {
describe("markdown messages should round-trip if they contain", function () {
test.each([
["newlines", "hello\nworld"],
["pills", "text message for @room"],
@ -85,7 +81,7 @@ describe('editor/roundtrip', function() {
["nested quotations", "saying\n\n> > foo\n\n> NO\n\nis valid"],
["quotations", "saying\n\n> NO\n\nis valid"],
["links", "click [this](http://example.com/)!"],
])('%s', async (_name, markdown) => {
])("%s", async (_name, markdown) => {
expect(await roundTripMarkdown(markdown)).toEqual(markdown);
});
@ -109,7 +105,7 @@ describe('editor/roundtrip', function() {
// Backslashes get doubled
["backslashes", "C:\\Program Files"],
// Deletes the whitespace
['newlines with trailing and leading whitespace', "hello \n world"],
["newlines with trailing and leading whitespace", "hello \n world"],
// Escapes the underscores
["underscores within a word", "abso_fragging_lutely"],
// Includes the trailing text into the quotation
@ -117,17 +113,16 @@ describe('editor/roundtrip', function() {
["quotations without separating newlines", "saying\n> NO\nis valid"],
// Removes trailing and leading whitespace
["quotations with trailing and leading whitespace", "saying \n\n> NO\n\n is valid"],
])('%s', async (_name, markdown) => {
])("%s", async (_name, markdown) => {
expect(await roundTripMarkdown(markdown)).toEqual(markdown);
});
it('styling, but * becomes _ and __ becomes **', async function() {
expect(await roundTripMarkdown("__bold__ and *emphasised*"))
.toEqual("**bold** and _emphasised_");
it("styling, but * becomes _ and __ becomes **", async function () {
expect(await roundTripMarkdown("__bold__ and *emphasised*")).toEqual("**bold** and _emphasised_");
});
});
describe('HTML messages should round-trip if they contain', function() {
describe("HTML messages should round-trip if they contain", function () {
test.each([
["backslashes", "C:\\Program Files"],
[
@ -140,7 +135,7 @@ describe('editor/roundtrip', function() {
["code blocks with surrounding text", "<p>a</p>\n<pre><code>a\ny;\n</code></pre>\n<p>b</p>\n"],
["code blocks", "<pre><code>a\ny;\n</code></pre>\n"],
["code blocks containing markdown", "<pre><code>__init__.py\n</code></pre>\n"],
["code blocks with language specifier", "<pre><code class=\"language-bash\">__init__.py\n</code></pre>\n"],
["code blocks with language specifier", '<pre><code class="language-bash">__init__.py\n</code></pre>\n'],
["paragraphs including formatting", "<p>one</p>\n<p>t <em>w</em> o</p>\n"],
["paragraphs", "<p>one</p>\n<p>two</p>\n"],
["links", "http://more.example.com/"],
@ -149,7 +144,7 @@ describe('editor/roundtrip', function() {
["formatting within a word", "abso<strong>fragging</strong>lutely"],
["formatting", "This <em>is</em> im<strong>port</strong>ant"],
["line breaks", "one<br>two"],
])('%s', async (_name, html) => {
])("%s", async (_name, html) => {
expect(await roundTripHtml(html)).toEqual(html);
});
@ -163,7 +158,7 @@ describe('editor/roundtrip', function() {
["paragraphs without newlines", "<p>one</p><p>two</p>"],
// Inserts a code block
["nested lists", "<ol>\n<li>asd</li>\n<li>\n<ul>\n<li>fgd</li>\n<li>sdf</li>\n</ul>\n</li>\n</ol>\n"],
])('%s', async (_name, html) => {
])("%s", async (_name, html) => {
expect(await roundTripHtml(html)).toEqual(html);
});
});

View file

@ -18,84 +18,84 @@ import EditorModel from "../../src/editor/model";
import { htmlSerializeIfNeeded } from "../../src/editor/serialize";
import { createPartCreator } from "./mock";
describe('editor/serialize', function() {
describe('with markdown', function() {
it('user pill turns message into html', function() {
describe("editor/serialize", function () {
describe("with markdown", function () {
it("user pill turns message into html", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.userPill("Alice", "@alice:hs.tld")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe("<a href=\"https://matrix.to/#/@alice:hs.tld\">Alice</a>");
expect(html).toBe('<a href="https://matrix.to/#/@alice:hs.tld">Alice</a>');
});
it('room pill turns message into html', function() {
it("room pill turns message into html", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.roomPill("#room:hs.tld")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe("<a href=\"https://matrix.to/#/#room:hs.tld\">#room:hs.tld</a>");
expect(html).toBe('<a href="https://matrix.to/#/#room:hs.tld">#room:hs.tld</a>');
});
it('@room pill turns message into html', function() {
it("@room pill turns message into html", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.atRoomPill("@room")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBeFalsy();
});
it('any markdown turns message into html', function() {
it("any markdown turns message into html", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("*hello* world")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe("<em>hello</em> world");
});
it('displaynames ending in a backslash work', function() {
it("displaynames ending in a backslash work", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.userPill("Displayname\\", "@user:server")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe("<a href=\"https://matrix.to/#/@user:server\">Displayname\\</a>");
expect(html).toBe('<a href="https://matrix.to/#/@user:server">Displayname\\</a>');
});
it('displaynames containing an opening square bracket work', function() {
it("displaynames containing an opening square bracket work", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.userPill("Displayname[[", "@user:server")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe("<a href=\"https://matrix.to/#/@user:server\">Displayname[[</a>");
expect(html).toBe('<a href="https://matrix.to/#/@user:server">Displayname[[</a>');
});
it('displaynames containing a closing square bracket work', function() {
it("displaynames containing a closing square bracket work", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.userPill("Displayname]", "@user:server")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe("<a href=\"https://matrix.to/#/@user:server\">Displayname]</a>");
expect(html).toBe('<a href="https://matrix.to/#/@user:server">Displayname]</a>');
});
it('escaped markdown should not retain backslashes', function() {
it("escaped markdown should not retain backslashes", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain('\\*hello\\* world')], pc);
const model = new EditorModel([pc.plain("\\*hello\\* world")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe('*hello* world');
expect(html).toBe("*hello* world");
});
it('escaped markdown should convert HTML entities', function() {
it("escaped markdown should convert HTML entities", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain('\\*hello\\* world < hey world!')], pc);
const model = new EditorModel([pc.plain("\\*hello\\* world < hey world!")], pc);
const html = htmlSerializeIfNeeded(model, {});
expect(html).toBe('*hello* world &lt; hey world!');
expect(html).toBe("*hello* world &lt; hey world!");
});
});
describe('with plaintext', function() {
it('markdown remains plaintext', function() {
describe("with plaintext", function () {
it("markdown remains plaintext", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("*hello* world")], pc);
const html = htmlSerializeIfNeeded(model, { useMarkdown: false });
expect(html).toBe("*hello* world");
});
it('markdown should retain backslashes', function() {
it("markdown should retain backslashes", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain('\\*hello\\* world')], pc);
const model = new EditorModel([pc.plain("\\*hello\\* world")], pc);
const html = htmlSerializeIfNeeded(model, { useMarkdown: false });
expect(html).toBe('\\*hello\\* world');
expect(html).toBe("\\*hello\\* world");
});
it('markdown should convert HTML entities', function() {
it("markdown should convert HTML entities", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain('\\*hello\\* world < hey world!')], pc);
const model = new EditorModel([pc.plain("\\*hello\\* world < hey world!")], pc);
const html = htmlSerializeIfNeeded(model, { useMarkdown: false });
expect(html).toBe('\\*hello\\* world &lt; hey world!');
expect(html).toBe("\\*hello\\* world &lt; hey world!");
});
it('plaintext remains plaintext even when forcing html', function() {
it("plaintext remains plaintext even when forcing html", function () {
const pc = createPartCreator();
const model = new EditorModel([pc.plain("hello world")], pc);
const html = htmlSerializeIfNeeded(model, { forceHTML: true, useMarkdown: false });