Apply prettier formatting
This commit is contained in:
parent
1cac306093
commit
526645c791
1576 changed files with 65385 additions and 62478 deletions
|
@ -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);
|
||||
|
|
|
@ -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 = "> <del>no formatting here</del>";
|
||||
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator(), { shouldEscape: false }));
|
||||
expect(parts.length).toBe(1);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = []) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 < hey world!');
|
||||
expect(html).toBe("*hello* world < 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 < hey world!');
|
||||
expect(html).toBe("\\*hello\\* world < 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 });
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue