git » sdk » commit 4d740c8

More unit tests

author Stephen Paul Weber
2026-03-26 02:34:43 UTC
committer Stephen Paul Weber
2026-03-26 02:59:40 UTC
parent 2d22bbc6945837598ff8a34aa7916a875b567712

More unit tests

test/TestAll.hx +4 -0
test/TestClient.hx +71 -0
test/TestJID.hx +77 -0
test/TestReaction.hx +30 -0
test/TestStanza.hx +51 -1
test/TestStringUtil.hx +20 -0
test/TestUtil.hx +36 -0

diff --git a/test/TestAll.hx b/test/TestAll.hx
index 83f46de..a7c6805 100644
--- a/test/TestAll.hx
+++ b/test/TestAll.hx
@@ -13,6 +13,10 @@ class TestAll {
 			new TestClient(),
 			new TestXEP0393(),
 			new TestEmojiUtil(),
+			new TestJID(),
+			new TestStringUtil(),
+			new TestUtil(),
+			new TestReaction(),
 		]);
 	}
 }
diff --git a/test/TestClient.hx b/test/TestClient.hx
index 61f3d97..f0cf8f6 100644
--- a/test/TestClient.hx
+++ b/test/TestClient.hx
@@ -5,8 +5,11 @@ import utest.Async;
 
 import borogove.Client;
 import borogove.Stanza;
+import borogove.Chat;
 import borogove.persistence.Dummy;
 
+using Lambda;
+
 @:access(borogove)
 class TestClient extends utest.Test {
 	public function testAccountId() {
@@ -191,4 +194,72 @@ class TestClient extends utest.Test {
 
 		client.stream.onStanza(new Stanza("message", { xmlns: "jabber:client", from: "test2@example.com", id: "localid"}).textTag("body", "hi"));
 	}
+
+	public function testEmptyAccountId() {
+		final persistence = new Dummy();
+		Assert.raises(() -> new Client("", persistence), String);
+		Assert.raises(() -> new Client(null, persistence), String);
+	}
+
+	public function testGetChatsFilter() {
+		final persistence = new Dummy();
+		final client = new Client("test@example.com", persistence);
+		final chat1 = client.getDirectChat("test1@example.com");
+		final chat2 = client.getDirectChat("test2@example.com");
+
+		Assert.equals(2, client.getChats().length);
+
+		chat1.close();
+		Assert.equals(1, client.getChats().length);
+		Assert.equals("test2@example.com", client.getChats()[0].chatId);
+	}
+
+	public function testChatsUpdateEvent(async: Async) {
+		final persistence = new Dummy();
+		final client = new Client("test@example.com", persistence);
+		client.on("chats/update", (chats: Array<Chat>) -> {
+			final friendChat = chats.find(c -> c.chatId == "friend@example.com");
+			if (friendChat != null) {
+				Assert.equals("friend@example.com", friendChat.chatId);
+				async.done();
+			}
+			return EventHandled;
+		});
+
+		client.getDirectChat("friend@example.com");
+	}
+
+	public function testPresenceSubscription(async: Async) {
+		final persistence = new Dummy();
+		final client = new Client("test@example.com", persistence);
+		client.inSync = true;
+
+		client.stream.on("sendStanza", (stanza: Stanza) -> {
+			if (stanza.name == "iq" && stanza.findChild("{http://jabber.org/protocol/disco#info}query") != null) {
+				client.stream.onStanza(
+					new Stanza("iq", { type: "result", to: "test@example.com", id: stanza.attr.get("id"), from: "stranger@example.com", xmlns: "jabber:client" })
+						.tag("query", { xmlns: "http://jabber.org/protocol/disco#info" })
+							.tag("identity", { category: "client", type: "pc", name: "Stranger" }).up()
+						.up()
+				);
+			}
+			return EventHandled;
+		});
+
+		client.on("chats/update", (chats: Array<Chat>) -> {
+			final strangerChat = chats.find(c -> c.chatId == "stranger@example.com");
+			if (strangerChat != null && strangerChat.uiState == Invited) {
+				Assert.equals("stranger@example.com", strangerChat.chatId);
+				Assert.equals("Stranger (stranger@example.com)", strangerChat.getDisplayName());
+				Assert.equals(Invited, strangerChat.uiState);
+				async.done();
+			}
+			return EventHandled;
+		});
+
+		client.stream.onStanza(
+			new Stanza("presence", { from: "stranger@example.com", type: "subscribe", xmlns: "jabber:client" })
+				.textTag("nick", "Stranger", { xmlns: "http://jabber.org/protocol/nick" })
+		);
+	}
 }
diff --git a/test/TestJID.hx b/test/TestJID.hx
new file mode 100644
index 0000000..37a1a1a
--- /dev/null
+++ b/test/TestJID.hx
@@ -0,0 +1,77 @@
+package test;
+
+import utest.Assert;
+import borogove.JID;
+
+class TestJID extends utest.Test {
+	public function testParse() {
+		var jid = JID.parse("user@example.com");
+		Assert.equals("user", jid.node);
+		Assert.equals("example.com", jid.domain);
+		Assert.isNull(jid.resource);
+
+		jid = JID.parse("user@example.com/mobile");
+		Assert.equals("user", jid.node);
+		Assert.equals("example.com", jid.domain);
+		Assert.equals("mobile", jid.resource);
+
+		jid = JID.parse("example.com");
+		Assert.isNull(jid.node);
+		Assert.equals("example.com", jid.domain);
+		Assert.isNull(jid.resource);
+
+		jid = JID.parse("example.com/resource");
+		Assert.isNull(jid.node);
+		Assert.equals("example.com", jid.domain);
+		Assert.equals("resource", jid.resource);
+	}
+
+	public function testEscape() {
+		// Test that escaping works in constructor
+		var jid = new JID("node with space", "example.com");
+		Assert.equals("node\\20with\\20space", jid.node);
+		Assert.equals("node\\20with\\20space@example.com", jid.asString());
+
+		jid = new JID("d'artagnan", "example.com");
+		Assert.equals("d\\27artagnan", jid.node);
+
+		jid = new JID("alice@wonderland", "example.com");
+		Assert.equals("alice\\40wonderland", jid.node);
+
+		// Test parsing escaped JIDs
+		jid = JID.parse("node\\20with\\20space@example.com");
+		Assert.equals("node\\20with\\20space", jid.node);
+	}
+
+	public function testUtilityMethods() {
+		var jid = new JID("user", "example.com", "resource", true);
+		Assert.isFalse(jid.isBare());
+		Assert.isFalse(jid.isDomain());
+		Assert.isTrue(jid.isValid());
+
+		var bare = jid.asBare();
+		Assert.isTrue(bare.isBare());
+		Assert.equals("user@example.com", bare.asString());
+
+		var withNewResource = bare.withResource("new");
+		Assert.equals("new", withNewResource.resource);
+		Assert.equals("user@example.com/new", withNewResource.asString());
+
+		var domainOnly = new JID(null, "example.com");
+		Assert.isTrue(domainOnly.isDomain());
+		Assert.isTrue(domainOnly.isBare());
+		Assert.equals("example.com", domainOnly.asString());
+
+		var invalid = new JID(null, "localhost");
+		Assert.isFalse(invalid.isValid());
+	}
+
+	public function testEquals() {
+		var jid1 = new JID("user", "example.com", "res", true);
+		var jid2 = new JID("user", "example.com", "res", true);
+		var jid3 = new JID("user", "example.com", "other", true);
+
+		Assert.isTrue(jid1.equals(jid2));
+		Assert.isFalse(jid1.equals(jid3));
+	}
+}
diff --git a/test/TestReaction.hx b/test/TestReaction.hx
new file mode 100644
index 0000000..b9eacf5
--- /dev/null
+++ b/test/TestReaction.hx
@@ -0,0 +1,30 @@
+package test;
+
+import utest.Assert;
+import borogove.Reaction;
+
+@:access(borogove.Reaction)
+class TestReaction extends utest.Test {
+	public function testNormalization() {
+		// 👍 with variation selector 16 (\u{fe0f})
+		var r = Reaction.unicode("👍\u{fe0f}");
+
+		// Internal text should have it stripped
+		Assert.equals("👍", r.text);
+
+		// Render should add it back
+		var rendered = r.render((text) -> text, (name, uri) -> "");
+		Assert.equals("👍\u{fe0f}", rendered);
+	}
+
+	public function testCustomEmoji() {
+		var r = CustomEmojiReaction.custom("tada", "https://example.com/tada.png");
+
+		var rendered = r.render(
+			(text) -> "text:" + text,
+			(name, uri) -> "image:" + name + ":" + uri
+		);
+
+		Assert.equals("image:tada:https://example.com/tada.png", rendered);
+	}
+}
diff --git a/test/TestStanza.hx b/test/TestStanza.hx
index 7ca0289..d905cb7 100644
--- a/test/TestStanza.hx
+++ b/test/TestStanza.hx
@@ -1,10 +1,10 @@
 package test;
 
-
 import utest.Assert;
 import utest.Async;
 import borogove.Stanza;
 
+@:access(borogove.Stanza)
 class TestStanza extends utest.Test {
 	public function testRemoveChildren() {
 		final s = new Stanza("test", { xmlns: "urn:example:foo" })
@@ -29,4 +29,54 @@ class TestStanza extends utest.Test {
 		Assert.equals(false, Stanza.parseXmlBool("false"));
 		Assert.equals(false, Stanza.parseXmlBool("0"));
 	}
+
+	public function testFluentApi() {
+		final s = new Stanza("root")
+			.tag("child", { id: "1" })
+				.text("hello")
+				.up()
+			.tag("child", { id: "2" })
+				.tag("grandchild")
+					.text("world")
+				.up()
+			.up();
+
+		Assert.equals(2, s.allTags("child").length);
+		var secondChild = s.allTags("child")[1];
+		Assert.equals("2", secondChild.attr.get("id"));
+		Assert.equals("world", secondChild.getChildText("grandchild"));
+	}
+
+	public function testFind() {
+		final s = new Stanza("root")
+			.tag("person", { name: "Alice" })
+				.textTag("email", "alice@example.com")
+				.up()
+			.tag("person", { name: "Bob" })
+				.textTag("email", "bob@example.com")
+				.up();
+
+		Assert.equals("Alice", s.findText("person@name"));
+		Assert.equals("alice@example.com", s.findText("person/email#"));
+
+		var person = s.findChild("person");
+		Assert.equals("Alice", person.attr.get("name"));
+	}
+
+	public function testClone() {
+		final s = new Stanza("root")
+			.tag("child")
+				.text("original")
+			.up();
+
+		final cloned = s.clone();
+		Assert.equals(s.serialize(), cloned.serialize());
+
+		// Modify original
+		s.allTags("child")[0].children = [];
+		s.allTags("child")[0].text("modified");
+
+		Assert.equals("modified", s.getChildText("child"));
+		Assert.equals("original", cloned.getChildText("child"));
+	}
 }
diff --git a/test/TestStringUtil.hx b/test/TestStringUtil.hx
new file mode 100644
index 0000000..9ad7697
--- /dev/null
+++ b/test/TestStringUtil.hx
@@ -0,0 +1,20 @@
+package test;
+
+import utest.Assert;
+import borogove.StringUtil;
+
+class TestStringUtil extends utest.Test {
+	public function testCodepointArray() {
+		Assert.same(["a", "b", "c"], StringUtil.codepointArray("abc"));
+		Assert.same(["👍", "!", "👋"], StringUtil.codepointArray("👍!👋"));
+		Assert.same(["\u{1F601}", "\u{1F602}"], StringUtil.codepointArray("\u{1F601}\u{1F602}"));
+		Assert.same([], StringUtil.codepointArray(""));
+	}
+
+	public function testRawCodepointArray() {
+		Assert.same([97, 98, 99], StringUtil.rawCodepointArray("abc"));
+		// 👍 is 0x1F44D (128077 decimal), 👋 is 0x1F44B (128075 decimal)
+		Assert.same([128077, 33, 128075], StringUtil.rawCodepointArray("👍!👋"));
+		Assert.same([], StringUtil.rawCodepointArray(""));
+	}
+}
diff --git a/test/TestUtil.hx b/test/TestUtil.hx
new file mode 100644
index 0000000..c3bcd44
--- /dev/null
+++ b/test/TestUtil.hx
@@ -0,0 +1,36 @@
+package test;
+
+import utest.Assert;
+import borogove.Util;
+
+class TestUtil extends utest.Test {
+	public function testXmlEscape() {
+		Assert.equals("foo &amp; bar &lt; baz &gt;", borogove.Util.xmlEscape("foo & bar < baz >"));
+		Assert.equals("nothing", borogove.Util.xmlEscape("nothing"));
+		Assert.equals("", borogove.Util.xmlEscape(""));
+	}
+
+	public function testUriDecode() {
+		Assert.equals("a b", borogove.Util.uriDecode("a%20b"));
+		Assert.equals("a+b", borogove.Util.uriDecode("a+b")); // Should NOT decode + as space
+		Assert.equals("!", borogove.Util.uriDecode("%21"));
+		Assert.equals("hello", borogove.Util.uriDecode("hello"));
+	}
+
+	public function testCapitalize() {
+		Assert.equals("Hello", Util.capitalize("hello"));
+		Assert.equals("Hello", Util.capitalize("Hello"));
+		Assert.equals("A", Util.capitalize("a"));
+		Assert.equals("", Util.capitalize(""));
+		Assert.isNull(Util.capitalize(null));
+	}
+
+	public function testSearchHelpers() {
+		final arr = [1, 2, 3, 4, 5];
+		Assert.isTrue(borogove.Util.existsFast(arr, (x) -> x == 3));
+		Assert.isFalse(borogove.Util.existsFast(arr, (x) -> x == 10));
+
+		Assert.equals(4, borogove.Util.findFast(arr, (x) -> x > 3));
+		Assert.isNull(borogove.Util.findFast(arr, (x) -> x > 10));
+	}
+}