diff options
Diffstat (limited to 'comm/chat/modules/test')
-rw-r--r-- | comm/chat/modules/test/test_InteractiveBrowser.js | 280 | ||||
-rw-r--r-- | comm/chat/modules/test/test_NormalizedMap.js | 80 | ||||
-rw-r--r-- | comm/chat/modules/test/test_filtering.js | 479 | ||||
-rw-r--r-- | comm/chat/modules/test/test_imThemes.js | 342 | ||||
-rw-r--r-- | comm/chat/modules/test/test_jsProtoHelper.js | 159 | ||||
-rw-r--r-- | comm/chat/modules/test/test_otrlib.js | 21 | ||||
-rw-r--r-- | comm/chat/modules/test/xpcshell.ini | 10 |
7 files changed, 1371 insertions, 0 deletions
diff --git a/comm/chat/modules/test/test_InteractiveBrowser.js b/comm/chat/modules/test/test_InteractiveBrowser.js new file mode 100644 index 0000000000..eb39d7048b --- /dev/null +++ b/comm/chat/modules/test/test_InteractiveBrowser.js @@ -0,0 +1,280 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { InteractiveBrowser, CancelledError } = ChromeUtils.importESModule( + "resource:///modules/InteractiveBrowser.sys.mjs" +); +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +add_task(async function test_waitForRedirectOnLocationChange() { + const url = "https://example.com"; + const promptText = "lorem ipsum"; + const { window, webProgress } = getRequestStubs(); + + const observeTopic = TestUtils.topicObserved("browser-request"); + let resolved = false; + const request = InteractiveBrowser.waitForRedirect(url, promptText).then( + redirectUrl => { + resolved = true; + return redirectUrl; + } + ); + const [subject] = await observeTopic; + + subject.wrappedJSObject.loaded(window, webProgress); + await TestUtils.waitForTick(); + ok(webProgress.listener, "Progress listener added"); + equal(window.document.title, promptText, "Window title set"); + + const intermediate = "https://intermediate.example.com/"; + webProgress.listener.onLocationChange( + webProgress, + { + name: intermediate + 1, + }, + { + spec: intermediate + 1, + } + ); + ok( + webProgress.listener, + "Progress listener still there after intermediary redirect" + ); + ok(!resolved, "Still waiting for redirect"); + webProgress.listener.onStateChange( + webProgress, + { + name: intermediate + 2, + }, + Ci.nsIWebProgressListener.STATE_START, + null + ); + ok(webProgress.listener, "Listener still there after second redirect"); + ok(!resolved, "Still waiting for redirect 2"); + + const completionUrl = InteractiveBrowser.COMPLETION_URL + "/test?code=asdf"; + webProgress.listener.onLocationChange( + webProgress, + { + name: completionUrl, + }, + { + spec: completionUrl, + } + ); + + const redirectedUrl = await request; + ok(resolved, "Redirect complete"); + equal(redirectedUrl, completionUrl); + + ok(!webProgress.listener); + ok(window.closed); +}); + +add_task(async function test_waitForRedirectOnStateChangeStart() { + const url = "https://example.com"; + const promptText = "lorem ipsum"; + const { window, webProgress } = getRequestStubs(); + + const observeTopic = TestUtils.topicObserved("browser-request"); + let resolved = false; + const request = InteractiveBrowser.waitForRedirect(url, promptText).then( + redirectUrl => { + resolved = true; + return redirectUrl; + } + ); + const [subject] = await observeTopic; + + subject.wrappedJSObject.loaded(window, webProgress); + await TestUtils.waitForTick(); + ok(webProgress.listener, "Progress listener added"); + equal(window.document.title, promptText, "Window title set"); + + const intermediate = "https://intermediate.example.com/"; + webProgress.listener.onStateChange( + webProgress, + { + name: intermediate, + }, + Ci.nsIWebProgressListener.STATE_START, + null + ); + ok(webProgress.listener); + ok(!resolved); + + const completionUrl = InteractiveBrowser.COMPLETION_URL + "/test?code=asdf"; + webProgress.listener.onStateChange( + webProgress, + { + name: completionUrl, + }, + Ci.nsIWebProgressListener.STATE_START + ); + + const redirectedUrl = await request; + ok(resolved, "Redirect complete"); + equal(redirectedUrl, completionUrl); + + ok(!webProgress.listener); + ok(window.closed); +}); + +add_task(async function test_waitForRedirectOnStateChangeStart() { + const url = "https://example.com"; + const promptText = "lorem ipsum"; + const { window, webProgress } = getRequestStubs(); + + const observeTopic = TestUtils.topicObserved("browser-request"); + let resolved = false; + const request = InteractiveBrowser.waitForRedirect(url, promptText).then( + redirectUrl => { + resolved = true; + return redirectUrl; + } + ); + const [subject] = await observeTopic; + + subject.wrappedJSObject.loaded(window, webProgress); + await TestUtils.waitForTick(); + ok(webProgress.listener, "Progress listener added"); + equal(window.document.title, promptText, "Window title set"); + + const intermediate = "https://intermediate.example.com/"; + webProgress.listener.onStateChange( + webProgress, + { + name: intermediate, + }, + Ci.nsIWebProgressListener.STATE_IS_NETWORK, + null + ); + ok(webProgress.listener); + ok(!resolved); + + const completionUrl = InteractiveBrowser.COMPLETION_URL + "/test?code=asdf"; + webProgress.listener.onStateChange( + webProgress, + { + name: completionUrl, + }, + Ci.nsIWebProgressListener.STATE_IS_NETWORK + ); + + const redirectedUrl = await request; + ok(resolved, "Redirect complete"); + equal(redirectedUrl, completionUrl); + + ok(!webProgress.listener); + ok(window.closed); +}); + +add_task(async function test_waitForRedirectCancelled() { + const url = "https://example.com"; + const promptText = "lorem ipsum"; + const observeTopic = TestUtils.topicObserved("browser-request"); + const request = InteractiveBrowser.waitForRedirect(url, promptText); + const [subject] = await observeTopic; + + subject.wrappedJSObject.cancelled(); + + await rejects(request, CancelledError); +}); + +add_task(async function test_waitForRedirectImmediatelyAborted() { + const url = "https://example.com"; + const promptText = "lorem ipsum"; + const { window, webProgress } = getRequestStubs(); + + const observeTopic = TestUtils.topicObserved("browser-request"); + const request = InteractiveBrowser.waitForRedirect(url, promptText); + const [subject] = await observeTopic; + + subject.wrappedJSObject.loaded(window, webProgress); + subject.wrappedJSObject.cancelled(); + await TestUtils.waitForTick(); + ok(!webProgress.listener); + + await rejects(request, CancelledError); +}); + +add_task(async function test_waitForRedirectAbortEvent() { + const url = "https://example.com"; + const promptText = "lorem ipsum"; + const { window, webProgress } = getRequestStubs(); + + const observeTopic = TestUtils.topicObserved("browser-request"); + const request = InteractiveBrowser.waitForRedirect(url, promptText); + const [subject] = await observeTopic; + + subject.wrappedJSObject.loaded(window, webProgress); + await TestUtils.waitForTick(); + ok(webProgress.listener); + equal(window.document.title, promptText); + + subject.wrappedJSObject.cancelled(); + await rejects(request, CancelledError); + ok(!webProgress.listener); + ok(window.closed); +}); + +add_task(async function test_waitForRedirectAlreadyArrived() { + const url = "https://example.com"; + const completionUrl = InteractiveBrowser.COMPLETION_URL + "/test?code=asdf"; + const promptText = "lorem ipsum"; + const { window, webProgress } = getRequestStubs(); + window.initialURI = completionUrl; + + const observeTopic = TestUtils.topicObserved("browser-request"); + let resolved = false; + const request = InteractiveBrowser.waitForRedirect(url, promptText).then( + redirectUrl => { + resolved = true; + return redirectUrl; + } + ); + const [subject] = await observeTopic; + + subject.wrappedJSObject.loaded(window, webProgress); + const redirectedUrl = await request; + + equal(window.document.title, promptText, "Window title set"); + ok(resolved, "Redirect complete"); + equal(redirectedUrl, completionUrl); + + ok(!webProgress.listener); + ok(window.closed); +}); + +function getRequestStubs() { + const mocks = { + window: { + close() { + this.closed = true; + }, + document: { + getElementById() { + return { + currentURI: { + spec: mocks.window.initialURI, + }, + }; + }, + }, + initialURI: "", + }, + webProgress: { + addProgressListener(listener) { + this.listener = listener; + }, + removeProgressListener(listener) { + if (this.listener === listener) { + delete this.listener; + } + }, + }, + }; + return mocks; +} diff --git a/comm/chat/modules/test/test_NormalizedMap.js b/comm/chat/modules/test/test_NormalizedMap.js new file mode 100644 index 0000000000..cad5bcd4d8 --- /dev/null +++ b/comm/chat/modules/test/test_NormalizedMap.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { NormalizedMap } = ChromeUtils.importESModule( + "resource:///modules/NormalizedMap.sys.mjs" +); + +function test_setter_getter() { + let m = new NormalizedMap(aStr => aStr.toLowerCase()); + m.set("foo", "bar"); + m.set("BaZ", "blah"); + Assert.equal(m.has("FOO"), true); + Assert.equal(m.has("BaZ"), true); + Assert.equal(m.get("FOO"), "bar"); + + let keys = Array.from(m.keys()); + Assert.equal(keys[0], "foo"); + Assert.equal(keys[1], "baz"); + + let values = Array.from(m.values()); + Assert.equal(values[0], "bar"); + Assert.equal(values[1], "blah"); + + Assert.equal(m.size, 2); + + run_next_test(); +} + +function test_constructor() { + let k = new NormalizedMap( + aStr => aStr.toLowerCase(), + [ + ["A", 2], + ["b", 3], + ] + ); + Assert.equal(k.get("b"), 3); + Assert.equal(k.get("a"), 2); + Assert.equal(k.get("B"), 3); + Assert.equal(k.get("A"), 2); + + run_next_test(); +} + +function test_iterator() { + let k = new NormalizedMap(aStr => aStr.toLowerCase()); + k.set("FoO", "bar"); + + for (let [key, value] of k) { + Assert.equal(key, "foo"); + Assert.equal(value, "bar"); + } + + run_next_test(); +} + +function test_delete() { + let m = new NormalizedMap(aStr => aStr.toLowerCase()); + m.set("foo", "bar"); + m.set("BaZ", "blah"); + + Assert.equal(m.delete("blah"), false); + + Assert.equal(m.delete("FOO"), true); + Assert.equal(m.size, 1); + + Assert.equal(m.delete("baz"), true); + Assert.equal(m.size, 0); + + run_next_test(); +} + +function run_test() { + add_test(test_setter_getter); + add_test(test_constructor); + add_test(test_iterator); + add_test(test_delete); + + run_next_test(); +} diff --git a/comm/chat/modules/test/test_filtering.js b/comm/chat/modules/test/test_filtering.js new file mode 100644 index 0000000000..33c8fcf262 --- /dev/null +++ b/comm/chat/modules/test/test_filtering.js @@ -0,0 +1,479 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// These tests run into issues if there isn't a profile directory, see bug 1542397. +do_get_profile(); + +var { IMServices } = ChromeUtils.importESModule( + "resource:///modules/IMServices.sys.mjs" +); +var { + cleanupImMarkup, + createDerivedRuleset, + addGlobalAllowedTag, + removeGlobalAllowedTag, + addGlobalAllowedAttribute, + removeGlobalAllowedAttribute, + addGlobalAllowedStyleRule, + removeGlobalAllowedStyleRule, +} = ChromeUtils.importESModule("resource:///modules/imContentSink.sys.mjs"); + +var kModePref = "messenger.options.filterMode"; +var kStrictMode = 0, + kStandardMode = 1, + kPermissiveMode = 2; + +function run_test() { + let defaultMode = Services.prefs.getIntPref(kModePref); + + add_test(test_strictMode); + add_test(test_standardMode); + add_test(test_permissiveMode); + add_test(test_addGlobalAllowedTag); + add_test(test_addGlobalAllowedAttribute); + add_test(test_addGlobalAllowedStyleRule); + add_test(test_createDerivedRuleset); + + Services.prefs.setIntPref(kModePref, defaultMode); + run_next_test(); +} + +// Sanity check: a string without HTML markup shouldn't be modified. +function test_plainText() { + const strings = [ + "foo", + "foo ", // preserve trailing whitespace + " foo", // preserve leading indent + "<html>&", // keep escaped characters + ]; + for (let string of strings) { + Assert.equal(string, cleanupImMarkup(string)); + } +} + +function test_paragraphs() { + const strings = ["<p>foo</p><p>bar</p>", "<p>foo<br>bar</p>", "foo<br>bar"]; + for (let string of strings) { + Assert.equal(string, cleanupImMarkup(string)); + } +} + +function test_stripScripts() { + const strings = [ + ["<script>alert('hey')</script>", ""], + ["foo <script>alert('hey')</script>", "foo "], + ["<p onclick=\"alert('hey')\">foo</p>", "<p>foo</p>"], + ["<p onmouseover=\"alert('hey')\">foo</p>", "<p>foo</p>"], + ]; + for (let [input, expectedOutput] of strings) { + Assert.equal(expectedOutput, cleanupImMarkup(input)); + } +} + +function test_links() { + // http, https, ftp and mailto links should be preserved. + const ok = [ + "http://example.com/", + "https://example.com/", + "ftp://example.com/", + "mailto:foo@example.com", + ]; + for (let string of ok) { + string = '<a href="' + string + '">foo</a>'; + Assert.equal(string, cleanupImMarkup(string)); + } + + // other links should be removed + const bad = [ + "chrome://global/content/", + "about:", + "about:blank", + "foo://bar/", + "", + ]; + for (let string of bad) { + Assert.equal( + "<a>foo</a>", + cleanupImMarkup('<a href="' + string + '">foo</a>') + ); + } + + // keep link titles + let string = '<a title="foo bar">foo</a>'; + Assert.equal(string, cleanupImMarkup(string)); +} + +function test_table() { + const table = + "<table>" + + "<caption>test table</caption>" + + "<thead>" + + "<tr>" + + "<th>key</th>" + + "<th>data</th>" + + "</tr>" + + "</thead>" + + "<tbody>" + + "<tr>" + + "<td>lorem</td>" + + "<td>ipsum</td>" + + "</tr>" + + "</tbody>" + + "</table>"; + Assert.equal(table, cleanupImMarkup(table)); +} + +function test_allModes() { + test_plainText(); + test_paragraphs(); + test_stripScripts(); + test_links(); + // Remove random classes. + Assert.equal("<p>foo</p>", cleanupImMarkup('<p class="foobar">foo</p>')); + // Test unparsable style. + Assert.equal("<p>foo</p>", cleanupImMarkup('<p style="not-valid">foo</p>')); +} + +function test_strictMode() { + Services.prefs.setIntPref(kModePref, kStrictMode); + test_allModes(); + + // check that basic formatting is stripped in strict mode. + for (let tag of [ + "div", + "em", + "strong", + "b", + "i", + "u", + "s", + "span", + "code", + "ul", + "li", + "ol", + "cite", + "blockquote", + "del", + "strike", + "ins", + "sub", + "sup", + "pre", + "td", + "details", + "h1", + ]) { + Assert.equal("foo", cleanupImMarkup("<" + tag + ">foo</" + tag + ">")); + } + + // check that font settings are removed. + Assert.equal( + "foo", + cleanupImMarkup('<font face="Times" color="pink">foo</font>') + ); + Assert.equal( + "<p>foo</p>", + cleanupImMarkup('<p style="font-weight: bold;">foo</p>') + ); + + // Discard hr + Assert.equal("foobar", cleanupImMarkup("foo<hr>bar")); + + run_next_test(); +} + +function test_standardMode() { + Services.prefs.setIntPref(kModePref, kStandardMode); + test_allModes(); + test_table(); + + // check that basic formatting is kept in standard mode. + for (let tag of [ + "div", + "em", + "strong", + "b", + "i", + "u", + "s", + "span", + "code", + "ul", + "li", + "ol", + "cite", + "blockquote", + "del", + "sub", + "sup", + "pre", + "strike", + "ins", + "details", + ]) { + let string = "<" + tag + ">foo</" + tag + ">"; + Assert.equal(string, cleanupImMarkup(string)); + } + + // Keep special allowed classes. + for (let className of ["moz-txt-underscore", "moz-txt-tag"]) { + let string = '<span class="' + className + '">foo</span>'; + Assert.equal(string, cleanupImMarkup(string)); + } + + // Remove font settings + let font_string = '<font face="Times" color="pink" size="3">foo</font>'; + Assert.equal("foo", cleanupImMarkup(font_string)); + + // Discard hr + Assert.equal("foobar", cleanupImMarkup("foo<hr>bar")); + + const okCSS = ["font-style: italic", "font-weight: bold"]; + for (let css of okCSS) { + let string = '<span style="' + css + '">foo</span>'; + Assert.equal(string, cleanupImMarkup(string)); + } + // text-decoration is a shorthand for several text-decoration properties, but + // standard mode only allows text-decoration-line. + Assert.equal( + '<span style="text-decoration-line: underline;">foo</span>', + cleanupImMarkup('<span style="text-decoration: underline">foo</span>') + ); + + const badCSS = [ + "color: pink;", + "font-family: Times", + "font-size: larger", + "display: none", + "visibility: hidden", + "unsupported-by-gecko: blah", + ]; + for (let css of badCSS) { + Assert.equal( + "<span>foo</span>", + cleanupImMarkup('<span style="' + css + '">foo</span>') + ); + } + // The shorthand 'font' is decomposed to non-shorthand properties, + // and not recomposed as some non-shorthand properties are filtered out. + Assert.equal( + '<span style="font-style: normal; font-weight: normal;">foo</span>', + cleanupImMarkup('<span style="font: 15px normal">foo</span>') + ); + + // Discard headings + const heading1 = "test heading"; + Assert.equal(heading1, cleanupImMarkup(`<h1>${heading1}</h1>`)); + + // Setting the start number of an <ol> is allowed + const olWithOffset = '<ol start="2"><li>two</li><li>three</li></ol>'; + Assert.equal(olWithOffset, cleanupImMarkup(olWithOffset)); + + run_next_test(); +} + +function test_permissiveMode() { + Services.prefs.setIntPref(kModePref, kPermissiveMode); + test_allModes(); + test_table(); + + // Check that all formatting is kept in permissive mode. + for (let tag of [ + "div", + "em", + "strong", + "b", + "i", + "u", + "span", + "code", + "ul", + "li", + "ol", + "cite", + "blockquote", + "del", + "sub", + "sup", + "pre", + "strike", + "ins", + "details", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + ]) { + let string = "<" + tag + ">foo</" + tag + ">"; + Assert.equal(string, cleanupImMarkup(string)); + } + + // Keep special allowed classes. + for (let className of ["moz-txt-underscore", "moz-txt-tag"]) { + let string = '<span class="' + className + '">foo</span>'; + Assert.equal(string, cleanupImMarkup(string)); + } + + // Keep font settings + const fontAttributes = ['face="Times"', 'color="pink"', 'size="3"']; + for (let fontAttribute of fontAttributes) { + let string = "<font " + fontAttribute + ">foo</font>"; + Assert.equal(string, cleanupImMarkup(string)); + } + + // Allow hr + let hr_string = "foo<hr>bar"; + Assert.equal(hr_string, cleanupImMarkup(hr_string)); + + // Allow most CSS rules changing the text appearance. + const okCSS = [ + "font-style: italic", + "font-weight: bold", + "color: pink;", + "font-family: Times", + "font-size: larger", + ]; + for (let css of okCSS) { + let string = '<span style="' + css + '">foo</span>'; + Assert.equal(string, cleanupImMarkup(string)); + } + // text-decoration is a shorthand for several text-decoration properties, but + // permissive mode only allows text-decoration-color, text-decoration-line, + // and text-decoration-style. + Assert.equal( + '<span style="text-decoration-color: currentcolor; text-decoration-line: underline; text-decoration-style: solid;">foo</span>', + cleanupImMarkup('<span style="text-decoration: underline;">foo</span>') + ); + + // The shorthand 'font' is decomposed to non-shorthand properties, + // and not recomposed as some non-shorthand properties are filtered out. + Assert.equal( + '<span style="font-family: normal; font-size: 15px; ' + + 'font-style: normal; font-weight: normal;">foo</span>', + cleanupImMarkup('<span style="font: 15px normal">foo</span>') + ); + + // But still filter out dangerous CSS rules. + const badCSS = [ + "display: none", + "visibility: hidden", + "unsupported-by-gecko: blah", + ]; + for (let css of badCSS) { + Assert.equal( + "<span>foo</span>", + cleanupImMarkup('<span style="' + css + '">foo</span>') + ); + } + + run_next_test(); +} + +function test_addGlobalAllowedTag() { + Services.prefs.setIntPref(kModePref, kStrictMode); + + // Check that <hr> isn't allowed by default in strict mode. + // Note: we use <hr> instead of <img> to avoid mailnews' content policy + // messing things up. + Assert.equal("", cleanupImMarkup("<hr>")); + + // Allow <hr> without attributes. + addGlobalAllowedTag("hr"); + Assert.equal("<hr>", cleanupImMarkup("<hr>")); + Assert.equal("<hr>", cleanupImMarkup('<hr src="http://example.com/">')); + removeGlobalAllowedTag("hr"); + + // Allow <hr> with an unfiltered src attribute. + addGlobalAllowedTag("hr", { src: true }); + Assert.equal("<hr>", cleanupImMarkup('<hr alt="foo">')); + Assert.equal( + '<hr src="http://example.com/">', + cleanupImMarkup('<hr src="http://example.com/">') + ); + Assert.equal( + '<hr src="chrome://global/skin/img.png">', + cleanupImMarkup('<hr src="chrome://global/skin/img.png">') + ); + removeGlobalAllowedTag("hr"); + + // Allow <hr> with an src attribute taking only http(s) urls. + addGlobalAllowedTag("hr", { src: aValue => /^https?:/.test(aValue) }); + Assert.equal( + '<hr src="http://example.com/">', + cleanupImMarkup('<hr src="http://example.com/">') + ); + Assert.equal( + "<hr>", + cleanupImMarkup('<hr src="chrome://global/skin/img.png">') + ); + removeGlobalAllowedTag("hr"); + + run_next_test(); +} + +function test_addGlobalAllowedAttribute() { + Services.prefs.setIntPref(kModePref, kStrictMode); + + // Check that id isn't allowed by default in strict mode. + Assert.equal("<br>", cleanupImMarkup('<br id="foo">')); + + // Allow id unconditionally. + addGlobalAllowedAttribute("id"); + Assert.equal('<br id="foo">', cleanupImMarkup('<br id="foo">')); + removeGlobalAllowedAttribute("id"); + + // Allow id only with numbers. + addGlobalAllowedAttribute("id", aId => /^\d+$/.test(aId)); + Assert.equal('<br id="123">', cleanupImMarkup('<br id="123">')); + Assert.equal("<br>", cleanupImMarkup('<br id="foo">')); + removeGlobalAllowedAttribute("id"); + + run_next_test(); +} + +function test_addGlobalAllowedStyleRule() { + // We need at least the standard mode to have the style attribute allowed. + Services.prefs.setIntPref(kModePref, kStandardMode); + + // Check that clear isn't allowed by default in strict mode. + Assert.equal("<br>", cleanupImMarkup('<br style="clear: both;">')); + + // Allow clear. + addGlobalAllowedStyleRule("clear"); + Assert.equal( + '<br style="clear: both;">', + cleanupImMarkup('<br style="clear: both;">') + ); + removeGlobalAllowedStyleRule("clear"); + + run_next_test(); +} + +function test_createDerivedRuleset() { + Services.prefs.setIntPref(kModePref, kStandardMode); + + let rules = createDerivedRuleset(); + + let string = "<hr>"; + Assert.equal("", cleanupImMarkup(string)); + Assert.equal("", cleanupImMarkup(string, rules)); + rules.tags.hr = true; + Assert.equal(string, cleanupImMarkup(string, rules)); + + string = '<br id="123">'; + Assert.equal("<br>", cleanupImMarkup(string)); + Assert.equal("<br>", cleanupImMarkup(string, rules)); + rules.attrs.id = true; + Assert.equal(string, cleanupImMarkup(string, rules)); + + string = '<br style="clear: both;">'; + Assert.equal("<br>", cleanupImMarkup(string)); + Assert.equal("<br>", cleanupImMarkup(string, rules)); + rules.styles.clear = true; + Assert.equal(string, cleanupImMarkup(string, rules)); + + run_next_test(); +} diff --git a/comm/chat/modules/test/test_imThemes.js b/comm/chat/modules/test/test_imThemes.js new file mode 100644 index 0000000000..61171fe121 --- /dev/null +++ b/comm/chat/modules/test/test_imThemes.js @@ -0,0 +1,342 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { + initHTMLDocument, + insertHTMLForMessage, + getHTMLForMessage, + replaceHTMLForMessage, + wasNextMessage, + removeMessage, + isNextMessage, +} = ChromeUtils.importESModule("resource:///modules/imThemes.sys.mjs"); +const { MockDocument } = ChromeUtils.importESModule( + "resource://testing-common/MockDocument.sys.mjs" +); + +const BASIC_CONV_DOCUMENT_HTML = + '<!DOCTYPE html><html><body><div id="Chat"></div></body></html>'; + +add_task(function test_initHTMLDocument() { + const window = {}; + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + "<!DOCTYPE html><html><head></head><body></body></html>" + ); + Object.defineProperty(document, "defaultView", { + value: window, + }); + const conversation = { + title: "test", + }; + const theme = { + baseURI: "chrome://messenger-messagestyles/skin/test/", + variant: "default", + metadata: {}, + html: { + footer: "", + script: 'console.log("hi");', + }, + }; + initHTMLDocument(conversation, theme, document); + equal(typeof document.defaultView.convertTimeUnits, "function"); + equal(document.querySelector("base").href, theme.baseURI); + ok( + document.querySelector( + 'link[rel="stylesheet"][href="chrome://chat/skin/conv.css"]' + ) + ); + ok(document.querySelector('link[rel="stylesheet"][href="main.css"]')); + + equal(document.body.id, "ibcontent"); + ok(document.getElementById("Chat")); + equal(document.querySelector("script").src, theme.baseURI + "inline.js"); +}); + +add_task(function test_insertHTMLForMessage() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const html = '<div style="background: blue;">foo bar</div>'; + const message = {}; + insertHTMLForMessage(message, html, document, false); + const messageElement = document.querySelector("#Chat > div"); + strictEqual(messageElement._originalMsg, message); + equal(messageElement.style.backgroundColor, "blue"); + equal(messageElement.textContent, "foo bar"); + ok(!messageElement.dataset.isNext); +}); + +add_task(function test_insertHTMLForMessage_next() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const html = '<div style="background: blue;">foo bar</div>'; + const message = {}; + insertHTMLForMessage(message, html, document, true); + const messageElement = document.querySelector("#Chat > div"); + strictEqual(messageElement._originalMsg, message); + equal(messageElement.style.backgroundColor, "blue"); + equal(messageElement.textContent, "foo bar"); + ok(messageElement.dataset.isNext); +}); + +add_task(function test_getHTMLForMessage() { + const message = { + incoming: true, + system: false, + message: "foo bar", + who: "userId", + alias: "display name", + color: "#ffbbff", + }; + const theme = { + html: { + incomingContent: + '<span style="color: %senderColor%;">%sender%</span>%message%', + }, + }; + const html = getHTMLForMessage(message, theme, false, false); + equal( + html, + '<span style="color: #ffbbff;"><span class="ib-sender">display name</span></span><span class="ib-msg-txt">foo bar</span>' + ); +}); + +add_task(function test_replaceHTMLForMessage() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const html = '<div style="background: blue;">foo bar</div>'; + const message = { + remoteId: "foo", + }; + insertHTMLForMessage(message, html, document, false); + const messageElement = document.querySelector("#Chat > div"); + strictEqual(messageElement._originalMsg, message); + equal(messageElement.style.backgroundColor, "blue"); + equal(messageElement.textContent, "foo bar"); + equal(messageElement.dataset.remoteId, "foo"); + ok(!messageElement.dataset.isNext); + const updatedHtml = + '<div style="background: green;">lorem ipsum</div><div id="insert"></div>'; + const updatedMessage = { + remoteId: "foo", + }; + replaceHTMLForMessage(updatedMessage, updatedHtml, document, true); + const updatedMessageElement = document.querySelector("#Chat > div"); + strictEqual(updatedMessageElement._originalMsg, updatedMessage); + equal(updatedMessageElement.style.backgroundColor, "green"); + equal(updatedMessageElement.textContent, "lorem ipsum"); + equal(updatedMessageElement.dataset.remoteId, "foo"); + ok(updatedMessageElement.dataset.isNext); + ok( + !document.querySelector("#insert"), + "Insert anchor in template is ignored when replacing" + ); +}); + +add_task(function test_replaceHTMLForMessageWithoutExistingMessage() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const updatedHtml = '<div style="background: green;">lorem ipsum</div>'; + const updatedMessage = { + remoteId: "foo", + }; + replaceHTMLForMessage(updatedMessage, updatedHtml, document, false); + const updatedMessageElement = document.querySelector("#Chat > div"); + ok(!updatedMessageElement); +}); + +add_task(function test_replaceHTMLForMessageWithoutRemoteId() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const html = '<div style="background: blue;">foo bar</div>'; + const message = { + remoteId: "foo", + }; + insertHTMLForMessage(message, html, document, false); + const messageElement = document.querySelector("#Chat > div"); + strictEqual(messageElement._originalMsg, message); + equal(messageElement.style.backgroundColor, "blue"); + equal(messageElement.textContent, "foo bar"); + equal(messageElement.dataset.remoteId, "foo"); + ok(!messageElement.dataset.isNext); + const updatedHtml = '<div style="background: green;">lorem ipsum</div>'; + const updatedMessage = {}; + replaceHTMLForMessage(updatedMessage, updatedHtml, document, false); + const updatedMessageElement = document.querySelector("#Chat > div"); + strictEqual(updatedMessageElement._originalMsg, message); + equal(updatedMessageElement.style.backgroundColor, "blue"); + equal(updatedMessageElement.textContent, "foo bar"); + equal(updatedMessageElement.dataset.remoteId, "foo"); + ok(!updatedMessageElement.dataset.isNext); +}); + +add_task(function test_wasNextMessage_isNext() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const html = "<div>foo bar</div>"; + const message = { + remoteId: "foo", + }; + insertHTMLForMessage(message, html, document, true); + ok(wasNextMessage(message, document)); +}); + +add_task(function test_wasNextMessage_isNotNext() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const html = "<div>foo bar</div>"; + const message = { + remoteId: "foo", + }; + insertHTMLForMessage(message, html, document, false); + ok(!wasNextMessage(message, document)); +}); + +add_task(function test_wasNextMessage_noPreviousVersion() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const message = { + remoteId: "foo", + }; + ok(!wasNextMessage(message, document)); +}); + +add_task(function test_removeMessage() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const html = '<div style="background: blue;">foo bar</div>'; + const message = { + remoteId: "foo", + }; + insertHTMLForMessage(message, html, document, false); + const messageElement = document.querySelector("#Chat > div"); + strictEqual(messageElement._originalMsg, message); + equal(messageElement.style.backgroundColor, "blue"); + equal(messageElement.textContent, "foo bar"); + equal(messageElement.dataset.remoteId, "foo"); + ok(!messageElement.dataset.isNext); + removeMessage("foo", document); + const messageElements = document.querySelectorAll("#Chat > div"); + equal(messageElements.length, 0); +}); + +add_task(function test_removeMessage_noMatchingMessage() { + const document = MockDocument.createTestDocument( + "chrome://chat/content/conv.html", + BASIC_CONV_DOCUMENT_HTML + ); + const html = '<div style="background: blue;">foo bar</div>'; + const message = { + remoteId: "foo", + }; + insertHTMLForMessage(message, html, document, false); + const messageElement = document.querySelector("#Chat > div"); + strictEqual(messageElement._originalMsg, message); + equal(messageElement.style.backgroundColor, "blue"); + equal(messageElement.textContent, "foo bar"); + equal(messageElement.dataset.remoteId, "foo"); + ok(!messageElement.dataset.isNext); + removeMessage("bar", document); + const messageElements = document.querySelectorAll("#Chat > div"); + notEqual(messageElements.length, 0); +}); + +add_task(function test_isNextMessage() { + const theme = { + combineConsecutive: true, + metadata: {}, + combineConsecutiveInterval: 300, + }; + const messagePairs = [ + { + message: {}, + previousMessage: null, + isNext: false, + }, + { + message: { + system: true, + }, + previousMessage: { + system: true, + }, + isNext: true, + }, + { + message: { + who: "foo", + }, + previousMessage: { + who: "bar", + }, + isNext: false, + }, + { + message: { + outgoing: true, + }, + isNext: false, + }, + { + message: { + incoming: true, + }, + isNext: false, + }, + { + message: { + system: true, + }, + isNext: false, + }, + { + message: { + time: 100, + }, + previousMessage: { + time: 100, + }, + isNext: true, + }, + { + message: { + time: 300, + }, + previousMessage: { + time: 100, + }, + isNext: true, + }, + { + message: { + time: 500, + }, + previousMessage: { + time: 100, + }, + isNext: false, + }, + ]; + for (const { message, previousMessage = {}, isNext } of messagePairs) { + equal(isNextMessage(theme, message, previousMessage), isNext); + } +}); diff --git a/comm/chat/modules/test/test_jsProtoHelper.js b/comm/chat/modules/test/test_jsProtoHelper.js new file mode 100644 index 0000000000..b87ec27241 --- /dev/null +++ b/comm/chat/modules/test/test_jsProtoHelper.js @@ -0,0 +1,159 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var { GenericConvIMPrototype } = ChromeUtils.importESModule( + "resource:///modules/jsProtoHelper.sys.mjs" +); + +var _id = 0; +function Conversation(name) { + this._name = name; + this._observers = []; + this._date = Date.now() * 1000; + this.id = ++_id; +} +Conversation.prototype = { + __proto__: GenericConvIMPrototype, + _account: { + imAccount: { + protocol: { name: "Fake Protocol" }, + alias: "", + name: "Fake Account", + }, + ERROR(e) { + throw e; + }, + DEBUG() {}, + }, +}; + +// ROT13, used as an example transformation. +function rot13(aString) { + return aString.replace(/[a-zA-Z]/g, function (c) { + return String.fromCharCode( + c.charCodeAt(0) + (c.toLowerCase() < "n" ? 1 : -1) * 13 + ); + }); +} + +// A test that cancels a message before it can be sent. +add_task(function test_cancel_send_message() { + let conv = new Conversation(); + conv.dispatchMessage = function (aMsg) { + ok( + false, + "The message should have been halted in the conversation service." + ); + }; + + let sending = false; + conv.addObserver({ + observe(aObject, aTopic, aMsg) { + switch (aTopic) { + case "sending-message": + ok( + aObject.QueryInterface(Ci.imIOutgoingMessage), + "Wrong message type." + ); + aObject.cancelled = true; + sending = true; + break; + case "new-text": + ok( + false, + "No other notification should be fired for a cancelled message." + ); + break; + } + }, + }); + conv.sendMsg("Hi!"); + ok(sending, "The sending-message notification was never fired."); +}); + +// A test that ensures protocols get a chance to prepare a message before +// sending and displaying. +add_task(function test_prpl_message_prep() { + let conv = new Conversation(); + conv.dispatchMessage = function (aMsg) { + this.writeMessage("user", aMsg, { outgoing: true }); + }; + + conv.prepareForSending = function (aMsg) { + ok(aMsg.QueryInterface(Ci.imIOutgoingMessage), "Wrong message type."); + equal(aMsg.message, msg, "Expected the original message."); + prepared = true; + return [prefix + aMsg.message]; + }; + + conv.prepareForDisplaying = function (aMsg) { + equal(aMsg.displayMessage, prefix + msg, "Expected the prefixed message."); + aMsg.displayMessage = aMsg.displayMessage.slice(prefix.length); + }; + + let msg = "Hi!"; + let prefix = "test> "; + + let prepared = false; + let receivedMsg = false; + conv.addObserver({ + observe(aObject, aTopic) { + if (aTopic === "preparing-message") { + equal(aObject.message, msg, "Expected the original message"); + } else if (aTopic === "sending-message") { + equal(aObject.message, prefix + msg, "Expected the prefixed message."); + } else if (aTopic === "new-text") { + ok(aObject.QueryInterface(Ci.prplIMessage), "Wrong message type."); + ok(prepared, "The message was not prepared before sending."); + equal(aObject.message, prefix + msg, "Expected the prefixed message."); + receivedMsg = true; + aObject.displayMessage = aObject.originalMessage; + conv.prepareForDisplaying(aObject); + equal(aObject.displayMessage, msg, "Expected the original message"); + } + }, + }); + + conv.sendMsg(msg); + ok(receivedMsg, "The new-text notification was never fired."); +}); + +// A test that ensures protocols can split messages before they are sent. +add_task(function test_split_message_before_sending() { + let msgCount = 0; + let prepared = false; + + let msg = "This is a looo\nooong message.\nThis one is short."; + let msgs = msg.split("\n"); + + let conv = new Conversation(); + conv.dispatchMessage = function (aMsg) { + equal(aMsg, msgs[msgCount++], "Sending an unexpected message."); + }; + conv.prepareForSending = function (aMsg) { + ok(aMsg.QueryInterface(Ci.imIOutgoingMessage), "Wrong message type."); + prepared = true; + return aMsg.message.split("\n"); + }; + + conv.sendMsg(msg); + + ok(prepared, "Message wasn't prepared for sending."); + equal(msgCount, 3, "Not enough messages were sent."); +}); + +add_task(function test_removeMessage() { + let didRemove = false; + let conv = new Conversation(); + conv.addObserver({ + observe(subject, topic, data) { + if (topic === "remove-text") { + equal(data, "foo"); + didRemove = true; + } + }, + }); + + conv.removeMessage("foo"); + ok(didRemove); +}); diff --git a/comm/chat/modules/test/test_otrlib.js b/comm/chat/modules/test/test_otrlib.js new file mode 100644 index 0000000000..4b321359f9 --- /dev/null +++ b/comm/chat/modules/test/test_otrlib.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test for libotr. + */ + +"use strict"; + +const { OTRLibLoader } = ChromeUtils.importESModule( + "resource:///modules/OTRLib.sys.mjs" +); + +/** + * Initialize libotr. + */ +add_setup(async function () { + let libOTR = await OTRLibLoader.init(); + Assert.ok(libOTR.otrl_version, "libotr did load"); +}); diff --git a/comm/chat/modules/test/xpcshell.ini b/comm/chat/modules/test/xpcshell.ini new file mode 100644 index 0000000000..d12004fd37 --- /dev/null +++ b/comm/chat/modules/test/xpcshell.ini @@ -0,0 +1,10 @@ +[DEFAULT] +head = +tail = + +[test_filtering.js] +[test_imThemes.js] +[test_InteractiveBrowser.js] +[test_jsProtoHelper.js] +[test_NormalizedMap.js] +[test_otrlib.js] |