`;
const test_cases = [
{
// Reverse linkify should preserve formatting when pasting HTML.
paste_html: html_with_formatting,
paste_text:
"x\n+\ny\n2\nx + y2\nx+y2 test https://github.com/zulip/zulip-desktop/pull/1359",
expected: "$$x + y2$$ ~~test~~ #D1359",
// When paste_html is a real value, there are two undo states:
// first undo reverses the reverse linkify (back to formatted text),
// second undo reverses the formatting (back to plain text).
expected_undo_texts: [
"x\n+\ny\n2\nx + y2\nx+y2 test https://github.com/zulip/zulip-desktop/pull/1359",
"$$x + y2$$ ~~test~~ https://github.com/zulip/zulip-desktop/pull/1359",
],
},
{
// Reverse linkify should work for URL only text.
paste_html: "",
paste_text: "https://github.com/zulip/zulip-desktop/pull/1359",
expected: "#D1359",
expected_undo_texts: ["https://github.com/zulip/zulip-desktop/pull/1359"],
},
{
// Reverse linkify should work in plain text with surrounding text.
paste_html: "",
paste_text: "https://github.com/zulip/zulip-desktop/pull/1359 dummy text.",
expected: "#D1359 dummy text.",
expected_undo_texts: ["https://github.com/zulip/zulip-desktop/pull/1359 dummy text."],
},
{
// Reverse linkify should work for alternative URL templates.
paste_html: "",
paste_text: "https://github.com/zulip/zulip-desktop/issues/42",
expected: "#D42",
expected_undo_texts: ["https://github.com/zulip/zulip-desktop/issues/42"],
},
];
for (const test_case of test_cases) {
const $textarea = $("textarea#compose-textarea");
// Put the cursor at the start with no selected text.
// The URL paste path checks this before it reaches reverse-linkify.
$textarea[0] = window.document.createElement("textarea");
$textarea[0].value = "";
inserted_text = undefined;
undo_texts = [];
const event = {
originalEvent: new ClipboardEvent({
clipboardData: {
getData(format) {
if (format === "text/html") {
return test_case.paste_html;
}
return test_case.paste_text;
},
},
}),
preventDefault() {},
stopPropagation() {},
};
compose_paste.paste_handler.call($textarea, event, noop);
assert.equal(inserted_text, test_case.expected, test_case.paste_text);
assert.deepEqual(
undo_texts,
test_case.expected_undo_texts,
`undo texts for: ${test_case.paste_text}`,
);
}
});
run_test("paste_handler_converter", () => {
/*
Pasting from another Zulip message
*/
global.document = window.document;
global.window = window;
global.Node = window.Node;
global.HTMLElement = window.HTMLElement;
global.HTMLAnchorElement = window.HTMLAnchorElement;
// Bold text
let input =
'love theZulipOrganization.';
assert.equal(
compose_paste.paste_handler_converter(input),
" love the **Zulip** **Organization**.",
);
// Inline code
input =
'TheJSDOMconstructor';
assert.equal(compose_paste.paste_handler_converter(input), "The `JSDOM` constructor");
// A python code block
input = `
zulip code block in python
print("hello")\nprint("world")
`;
assert.equal(
compose_paste.paste_handler_converter(input),
'zulip code block in python\n\n```Python\nprint("hello")\nprint("world")\n```',
);
// Single line in a code block
input =
'
single line
';
assert.equal(compose_paste.paste_handler_converter(input), "`single line`");
// No code formatting if the given text area has a backtick at the cursor position
input =
'
single line
';
assert.equal(
compose_paste.paste_handler_converter(input, {
val() {
return "e.g. `";
},
0: {
selectionStart: 6,
value: "e.g. `",
},
length: 1,
}),
"single line",
);
// No code formatting if the given text area has a backtick at the cursor position
input =
'
single line
';
assert.equal(
compose_paste.paste_handler_converter(input, {
val() {
return "`e.g. ` ```hi`` ``";
},
0: {
selectionStart: 19,
value: "`e.g. ` ```hi`` ``",
},
length: 1,
}),
"single line",
);
// No code formatting if the given text area has a opening backtick before the cursor position
input =
'
single line
';
assert.equal(
compose_paste.paste_handler_converter(input, {
val() {
return "e.g. ` ";
},
0: {
selectionStart: 7,
value: "e.g. ` ",
},
length: 1,
}),
"single line",
);
// Yes code formatting if the given text area does not have a backtick at the cursor position.
input =
'
single line
';
assert.equal(
compose_paste.paste_handler_converter(input, {
val() {
return "";
},
0: {
selectionStart: 0,
value: "",
},
length: 1,
}),
"`single line`",
);
// Yes code formatting if the given text area closes the code block before the cursor position
input =
'
single line
';
assert.equal(
compose_paste.paste_handler_converter(input, {
val() {
return "` e.g. ` ";
},
0: {
selectionStart: 9,
value: "` e.g. ` ",
},
length: 1,
}),
"`single line`",
);
// Yes code formatting if the given text area closes the code block before the cursor position
input =
'
single line
';
assert.equal(
compose_paste.paste_handler_converter(input, {
val() {
return "``` e.g. ``` ``hi`` ";
},
0: {
selectionStart: 20,
value: "``` e.g. ``` ``hi`` ",
},
length: 1,
}),
"`single line`",
);
// Raw links without custom text
input =
'https://zulip.readthedocs.io/en/latest/subsystems/logging.html';
assert.equal(
compose_paste.paste_handler_converter(input),
"https://zulip.readthedocs.io/en/latest/subsystems/logging.html",
);
// Links with custom text
input =
'Contributing guide';
assert.equal(
compose_paste.paste_handler_converter(input),
"[Contributing guide](https://zulip.readthedocs.io/en/latest/contributing/contributing.html)",
);
// Only numbered list (list style retained)
input =
'
';
assert.equal(compose_paste.paste_handler_converter(input), "Zulip overview");
// Italic text
input =
'normal text This text is italic';
assert.equal(compose_paste.paste_handler_converter(input), "normal text *This text is italic*");
// Strikethrough text
input =
'normal text This text is struck through';
assert.equal(
compose_paste.paste_handler_converter(input),
"normal text ~~This text is struck through~~",
);
// Emojis
input =
'emojis:Â :smile:Â :family_man_woman_girl:';
assert.equal(
compose_paste.paste_handler_converter(input),
"emojis:Â :smile:Â :family_man_woman_girl:",
);
// Nested lists
input =
'
From Words to Vectors: Understanding the Magic of Text Embedding
`;
assert.equal(
compose_paste.paste_handler_converter(input),
"From Words to Vectors: Understanding the Magic of Text Embedding",
);
// Check we don't double-convert HTML to text.
input = `turtles are cool`;
assert.equal(compose_paste.paste_handler_converter(input), "turtles are cool");
input = `<del>turtles are cool</del>`;
assert.equal(compose_paste.paste_handler_converter(input), "turtles are cool");
// 2 paragraphs with line break/s in between
input =
'
paragraph 1
paragraph 2
';
assert.equal(compose_paste.paste_handler_converter(input), "paragraph 1\n\nparagraph 2");
// Pasting from external sources
// Pasting list from GitHub
input =
'
Test list:
Item 1
Item 2
';
assert.equal(compose_paste.paste_handler_converter(input), "Test list:\n* Item 1\n* Item 2");
// Pasting list from VS Code
input =
'
Test list:
Item 1
Item 2
';
assert.equal(compose_paste.paste_handler_converter(input), "Test list:\n* Item 1\n* Item 2");
// Pasting markdown from VS Code where each line is wrapped by a
shouldn't insert an extra newline.
input = `\r\n\r\n
authentication-methods
export-and-import
postgresql
upload-backends
ssl-certificates
email
\r\n\r\n`;
assert.equal(
compose_paste.paste_handler_converter(input),
"authentication-methods\nexport-and-import\npostgresql\nupload-backends\nssl-certificates\nemail",
);
// Pasting from Google Sheets (remove 123';
assert.equal(compose_paste.paste_handler_converter(input), "123");
// Pasting a long, visually line-wrapped single-line message from Firefox should not insert extraneous newlines.
input = `\n
\n
At some point recently, Zulip changed such that copying a \nlong message includes hard newlines, rather than putting things all on \none line when they were on one line in the original message.
\n
\n\n`;
assert.equal(
compose_paste.paste_handler_converter(input),
"At some point recently, Zulip changed such that copying a long message includes hard newlines, rather than putting things all on one line when they were on one line in the original message.",
);
// Pasting from Excel
input = `\n\n\n\n\n\n\n\n\n\n
\n\n
\n
\n
$\n 20.00
\n
\n
\n
$7.00
\n
\n\n
\n\n`;
// Pasting from Excel using ^V should paste an image.
assert.ok(compose_paste.is_single_image(input));
// Pasting from Excel using ^⇧V should paste formatted text.
assert.equal(compose_paste.paste_handler_converter(input), " \n\n$ 20.00\n\n$ 7.00");
// Pasting from LibreOffice Calc should paste an image.
input = `
Kathleen
Hanner
Female
United States
Nereida
Magwood
Female
United States
`;
assert.ok(compose_paste.is_single_image(input));
// This contains three child elements inside the body tag, pasted
// from LibreOffice Writer, which is correctly classified as not an image.
input = `
ello world
X
as
Jak
J
Nm
,mn
,nnf
Adlk
Asn
,amns
Nm
Oi
Poi
B
Ijo
,mn,
;ih
Oug
Iu
G
Ug
Bkjb
Kjbk
;jbj
;jb;
Bkjb
Ugug
I9
68
0
90kjb
,bnbiu
Ofif
P8gp
pugp
`;
assert.ok(!compose_paste.is_single_image(input));
// has a single child element which is not a
pasted
// from LibreOffice Writer should get pasted normally.
input = `
Hello world this is some random text.
`;
assert.ok(!compose_paste.is_single_image(input));
// A single table pasted from LibreOffice Writer is incorrectly
// detected as a LibreOffice Calc table.
// See https://github.com/zulip/zulip/pull/34752/#discussion_r2113598064
input = `
Melgar
Female
UnitedStates
Weiland
Female
UnitedStates
Winward
Female
GreatBritain
`;
assert.ok(compose_paste.is_single_image(input));
// Copying an image and pasting it with `paste_html` containing other empty text nodes and
// comment nodes should still classify the `paste_html` as an image that needs to be uploaded.
input = `\n\n\n\n`;
assert.ok(compose_paste.is_single_image(input));
// Pasting from the mac terminal
input =
'
insertions
';
assert.equal(compose_paste.paste_handler_converter(input), "insertions");
// Zulip timestamp followed by text.
input =
'good better';
assert.equal(
compose_paste.paste_handler_converter(input),
" good better",
);
// Chrome-stripped timestamp: Chrome's clipboard serializer removes the
Test list: