geary/test/js/composer-page-state-test.vala
Niels De Graef c5630d0567 ComposerPageStateTest: Don't use NBSP directly
In some WebKitGTK versions, we were getting the following error from
within WebKit when running the `replace_non_breaking_space` test:

> SyntaxError Invalid character: '\0'

Doing some further investigation, it seems like webkit was choking on
the raw non-breaking space character (NBSP) that we were using in the
test's string literals, specifically the first byte of its raw value
`U+00A0`.

Work around this for now by generating the NBSP in Javascript itself,
rather than passing on the raw value.
2025-12-15 00:40:52 +00:00

432 lines
15 KiB
Vala

/*
* Copyright 2016 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class Composer.PageStateTest : Components.WebViewTestCase<Composer.WebView> {
public const string COMPLETE_BODY_TEMPLATE =
"""<div id="geary-body" dir="auto">%s<div><br></div><div><br></div></div><div id="geary-signature" dir="auto"></div>""";
public const string DIRTY_BODY_TEMPLATE =
"""
<div id="geary-body" dir="auto" contenteditable="true">%s<div><br></div><div><br></div></div>
<div id="geary-signature" class="geary-no-display" dir="auto" contenteditable="true"></div>
""";
public const string CLEAN_BODY_TEMPLATE = """<div id="geary-body" dir="auto">%s<div><br></div><div><br></div></div>""";
public PageStateTest() {
base("Composer.PageStateTest");
add_test("html_to_text", html_to_text);
add_test("html_to_text_with_quote", html_to_text_with_quote);
add_test("html_to_text_with_nested_quote", html_to_text_with_nested_quote);
add_test("html_to_text_with_blacklist", html_to_text_with_blacklist);
add_test("edit_context_font", edit_context_font);
add_test("edit_context_link", edit_context_link);
add_test("indent_line", indent_line);
add_test("clean_content", clean_content);
add_test("get_html", get_html);
add_test("get_text", get_text);
add_test("contains_keywords", contains_keywords);
// Depends contains_keywords and html_to_text_with_blacklist
add_test("contains_attachment_keywords", contains_attachment_keywords);
add_test("replace_non_breaking_space", replace_non_breaking_space);
try {
WebView.load_resources();
} catch (Error err) {
GLib.assert_not_reached();
}
}
public void html_to_text() throws Error {
load_body_fixture("<p>para</p>");
try {
assert(
Util.JS.to_string(
run_javascript(
@"ComposerPageState.htmlToText(window.document.body);"
)
) == "para\n\n\n\n"
);
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void html_to_text_with_quote() throws Error {
unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER;
load_body_fixture("<p>pre</p> <blockquote><p>quote</p></blockquote> <p>post</p>");
try {
assert(
Util.JS.to_string(
run_javascript(
"ComposerPageState.htmlToText(window.document.body);"
)
) == @"pre\n\n$(q_marker)quote\n$(q_marker)\npost\n\n\n\n"
);
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s", err.message);
assert_not_reached();
}
}
public void html_to_text_with_nested_quote() throws Error {
unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER;
load_body_fixture("<p>pre</p> <blockquote><p>quote1</p> <blockquote><p>quote2</p></blockquote></blockquote> <p>post</p>");
try {
assert(
Util.JS.to_string(
run_javascript(
"ComposerPageState.htmlToText(window.document.body)"
)
) == @"pre\n\n$(q_marker)quote1\n$(q_marker)\n$(q_marker)$(q_marker)quote2\n$(q_marker)$(q_marker)\npost\n\n\n\n"
);
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void html_to_text_with_blacklist() throws Error {
load_body_fixture("<p>pre</p> <blockquote><p>quote1</p> <blockquote><p>quote2</p></blockquote></blockquote> <p>post</p>");
try {
assert(
Util.JS.to_string(
run_javascript(
"ComposerPageState.htmlToText(window.document.body, [\"blockquote\"])"
)
) == @"pre\n\npost\n\n\n\n"
);
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void edit_context_link() throws Error {
string html = "<a id=\"test\" href=\"url\">para</a>";
load_body_fixture(html);
try {
assert(
Util.JS.to_string(
run_javascript(@"new EditContext(document.getElementById('test')).encode()")
).has_prefix("1;url;"));
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void edit_context_font() throws Error {
string html = "<p id=\"test\" style=\"font-family: Comic Sans; font-size: 144; color: #FF7F01\">para</p>";
load_body_fixture(html);
try {
assert(
Util.JS.to_string(
run_javascript(@"new EditContext(document.getElementById('test')).encode()")
) == "0;;Comic Sans;144;rgb(255, 127, 1)");
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void indent_line() throws Error {
load_body_fixture("""<span id="test">some text</span>""");
try {
run_javascript(@"SelectionUtil.selectNode(document.getElementById('test'))");
run_javascript(@"geary.indentLine()");
assert(
Util.JS.to_int32(
run_javascript(@"document.querySelectorAll('blockquote[type=cite]').length")
) == 1
);
assert(
Util.JS.to_string(
run_javascript(@"document.querySelectorAll('blockquote[type=cite]').item(0).innerText")
) == "some text"
);
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void contains_attachment_keywords() throws Error {
load_body_fixture_full("""
<blockquote>innerquote</blockquote>
<p>some text</p>
some text
""",
"<p>outerquote text</p>",
true
);
try {
assert(
Util.JS.to_bool(
run_javascript(@"geary.containsAttachmentKeyword(\"some\", \"subject text\");")
)
);
assert(
Util.JS.to_bool(
run_javascript(@"geary.containsAttachmentKeyword(\"subject\", \"subject text\");")
)
);
assert(
!Util.JS.to_bool(
run_javascript(@"geary.containsAttachmentKeyword(\"innerquote\", \"subject text\");")
)
);
assert(
!Util.JS.to_bool(
run_javascript(@"geary.containsAttachmentKeyword(\"outerquote\", \"subject text\");")
)
);
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void clean_content() throws GLib.Error {
// XXX split these up into multiple tests
load_body_fixture("""
http://example1.com
<p>http://example2.com</p>
<p>http://example3.com http://example4.com</p>
<a href="blarg">http://example5.com</a>
unknown://example6.com
I can send email through smtp.gmail.com:587 or through https://www.gmail.com/
""");
string expected = """
<a href="http://example1.com">http://example1.com</a>
<p><a href="http://example2.com">http://example2.com</a></p>
<p><a href="http://example3.com">http://example3.com</a> <a href="http://example4.com">http://example4.com</a></p>
<a href="blarg">http://example5.com</a>
unknown://example6.com
I can send email through smtp.gmail.com:587 or through <a href="https://www.gmail.com/">https://www.gmail.com/</a>
""";
run_javascript("geary.cleanContent();");
string result = Util.JS.to_string(
run_javascript("window.document.body.innerHTML;")
);
assert_equal(result, DIRTY_BODY_TEMPLATE.printf(expected));
}
public void get_html() throws Error {
string html = "<p>para</p>";
load_body_fixture(html);
try {
string result = Util.JS.to_string(
run_javascript(@"window.geary.getHtml();")
);
assert(result == CLEAN_BODY_TEMPLATE.printf(html));
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void get_text() throws Error {
load_body_fixture("<p>para</p>");
try {
assert(
Util.JS.to_string(
run_javascript(@"window.geary.getText();")
) == "para\n\n\n\n"
);
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void contains_keywords() throws Error {
load_body_fixture();
string complete_keys = """new Set(["keyword1", "keyword2"])""";
string suffix_keys = """new Set(["sf1", "sf2"])""";
try {
// Doesn't contain
assert(!Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('notcontained', $complete_keys, $suffix_keys);"
)
));
assert(!Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('not contained', $complete_keys, $suffix_keys);"
)
));
assert(!Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('not\tcontained', $complete_keys, $suffix_keys);"
)
));
assert(!Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('http://www.keyword1.com', $complete_keys, $suffix_keys);"
)
));
assert(!Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('http://www.something.com/something.sf1', $complete_keys, $suffix_keys);"
)
));
assert(!Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('sf1', $complete_keys, $suffix_keys);"
)
));
assert(!Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('.sf1', $complete_keys, $suffix_keys);"
)
));
// Does contain
assert(Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('keyword1', $complete_keys, $suffix_keys);"
)
));
assert(Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('keyword2 contained', $complete_keys, $suffix_keys);"
)
));
assert(Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('keyword2\tcontained', $complete_keys, $suffix_keys);"
)
));
assert(Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('keyword1.', $complete_keys, $suffix_keys);"
)
));
assert(Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('something.sf1', $complete_keys, $suffix_keys);"
)
));
assert(Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('something.something.sf2', $complete_keys, $suffix_keys);"
)
));
assert(!Util.JS.to_bool(run_javascript(
@"ComposerPageState.containsKeywords('http://something/else.sf2', $complete_keys, $suffix_keys);"
)
));
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
public void replace_non_breaking_space() throws Error {
load_body_fixture();
string nbsp = "String.fromCharCode(160)";
string single_nbsp = @"'a' + $(nbsp) + 'b'";
string multiple_nbsp = @"'a' + $(nbsp) + 'b' + $(nbsp) + 'c'";
try {
assert(
Util.JS.to_string(
run_javascript(@"ComposerPageState.replaceNonBreakingSpace($(single_nbsp));")
) == "a b");
assert(
Util.JS.to_string(
run_javascript(@"ComposerPageState.replaceNonBreakingSpace($(multiple_nbsp));")
) == "a b c");
} catch (Util.JS.Error err) {
print("Util.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
protected override Composer.WebView set_up_test_view() {
return new Composer.WebView(this.config);
}
protected override void load_body_fixture(string body = "") {
load_body_fixture_full(body, "", true);
}
protected void load_body_fixture_full(string body,
string quote,
bool top_posting) {
this.test_view.load_html_headless(body, quote, top_posting, false);
while (this.test_view.is_loading) {
Gtk.main_iteration();
}
}
}