git » sdk » commit 88bf4b1

Add more idb playwright tests

author Stephen Paul Weber
2026-04-08 06:05:32 UTC
committer Stephen Paul Weber
2026-04-08 06:05:32 UTC
parent cfae56f6c6f27a7293e0f3a64b1ea894a10077da

Add more idb playwright tests

Makefile +1 -1
borogove/persistence/MediaStoreCache.js +2 -3
test/idb.spec.ts +292 -7

diff --git a/Makefile b/Makefile
index 9348a1f..326fca4 100644
--- a/Makefile
+++ b/Makefile
@@ -50,7 +50,7 @@ npm: npm/borogove-browser.js npm/borogove.js borogove/persistence/IDB.js borogov
 	cd npm && npx tsc --esModuleInterop --lib esnext,dom --target esnext --preserveConstEnums --allowJs --checkJs -d index.ts
 
 playwright/.cache/borogove.js: npm
-	esbuild npm/index.js --bundle --format=esm "--alias:node:dns=@xmpp/resolve" "--footer:js=export { borogove_JID as JID }" --outfile=$@
+	esbuild npm/index.js --bundle --format=esm "--alias:node:dns=@xmpp/resolve" "--footer:js=export { borogove_JID as JID, borogove_ReactionUpdate as ReactionUpdate }" --outfile=$@
 
 playwright: playwright/.cache/borogove.js
 	npx playwright test
diff --git a/borogove/persistence/MediaStoreCache.js b/borogove/persistence/MediaStoreCache.js
index bc25fe3..0a40b59 100644
--- a/borogove/persistence/MediaStoreCache.js
+++ b/borogove/persistence/MediaStoreCache.js
@@ -1,9 +1,8 @@
 // This example MediaStore is written in JavaScript
 // so that SDK users can easily see how to write their own
 
-export default (cacheName) => {
-	var cache = null;
-	caches.open(cacheName).then((c) => cache = c);
+export default async (cacheName) => {
+	const cache = await caches.open(cacheName);
 
 	function mkNiUrl(hashAlgorithm, hashBytes) {
 		const b64url = btoa(Array.from(new Uint8Array(hashBytes), (x) => String.fromCodePoint(x)).join("")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
diff --git a/test/idb.spec.ts b/test/idb.spec.ts
index 501994c..7bafca0 100644
--- a/test/idb.spec.ts
+++ b/test/idb.spec.ts
@@ -9,7 +9,7 @@ test("1:1 come back ordered by sortId", async ({ page }) => {
 		const blob = new Blob([code], { type: 'text/javascript' });
 		const borogove = await import(URL.createObjectURL(blob));
 
-		const mediaStore = borogove.persistence.MediaStoreCache("snikket");
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
 		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
 
 		const builder = new borogove.ChatMessageBuilder({
@@ -55,7 +55,7 @@ test("getMessagesBefore the end: MUC come back ordered by sortId, PM by timestam
 		const blob = new Blob([code], { type: 'text/javascript' });
 		const borogove = await import(URL.createObjectURL(blob));
 
-		const mediaStore = borogove.persistence.MediaStoreCache("snikket");
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
 		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
 
 		const builder = new borogove.ChatMessageBuilder({
@@ -120,7 +120,7 @@ test("getMessagesBefore some point: MUC come back ordered by sortId, PM by times
 		const blob = new Blob([code], { type: 'text/javascript' });
 		const borogove = await import(URL.createObjectURL(blob));
 
-		const mediaStore = borogove.persistence.MediaStoreCache("snikket");
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
 		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
 
 		const builder = new borogove.ChatMessageBuilder({
@@ -199,7 +199,7 @@ test("getMessagesBefore a PM", async ({ page }) => {
 		const blob = new Blob([code], { type: 'text/javascript' });
 		const borogove = await import(URL.createObjectURL(blob));
 
-		const mediaStore = borogove.persistence.MediaStoreCache("snikket");
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
 		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
 
 		const builder = new borogove.ChatMessageBuilder({
@@ -277,7 +277,7 @@ test("getMessagesAfter the start: MUC come back ordered by sortId, PM by timesta
 		const blob = new Blob([code], { type: 'text/javascript' });
 		const borogove = await import(URL.createObjectURL(blob));
 
-		const mediaStore = borogove.persistence.MediaStoreCache("snikket");
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
 		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
 
 		const builder = new borogove.ChatMessageBuilder({
@@ -342,7 +342,7 @@ test("getMessagesAfter some point: MUC come back ordered by sortId, PM by timest
 		const blob = new Blob([code], { type: 'text/javascript' });
 		const borogove = await import(URL.createObjectURL(blob));
 
-		const mediaStore = borogove.persistence.MediaStoreCache("snikket");
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
 		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
 
 		const builder = new borogove.ChatMessageBuilder({
@@ -421,7 +421,7 @@ test("getMessagesAfter a PM", async ({ page }) => {
 		const blob = new Blob([code], { type: 'text/javascript' });
 		const borogove = await import(URL.createObjectURL(blob));
 
-		const mediaStore = borogove.persistence.MediaStoreCache("snikket");
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
 		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
 
 		const builder = new borogove.ChatMessageBuilder({
@@ -489,3 +489,288 @@ test("getMessagesAfter a PM", async ({ page }) => {
 	expect(result.length).toBe(1);
 	expect(result[0].serverId).toBe("4");
 });
+
+test("storeChats and getChats", async ({ page }) => {
+	page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" }));
+	const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8");
+	await page.goto("https://localhost/");
+	const result = await page.evaluate(async (code) => {
+		const blob = new Blob([code], { type: 'text/javascript' });
+		const borogove = await import(URL.createObjectURL(blob));
+
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
+		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
+
+		const chat = Object.create(borogove.DirectChat.prototype);
+		chat.chatId = "hatter@example.com";
+		chat.displayName = "The Mad Hatter";
+		chat.trusted = true;
+		chat.presence = new Map();
+
+		await persistence.storeChats("alice@example.com", [chat]);
+		return await persistence.getChats("alice@example.com");
+	}, code);
+
+	expect(result.length).toBe(1);
+	expect(result[0].chatId).toBe("hatter@example.com");
+	expect(result[0].displayName).toBe("The Mad Hatter");
+	expect(result[0].trusted).toBe(true);
+	expect(result[0].klass).toBe("DirectChat");
+});
+
+test("getMessage by serverId and localId", async ({ page }) => {
+	page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" }));
+	const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8");
+	await page.goto("https://localhost/");
+	const result = await page.evaluate(async (code) => {
+		const blob = new Blob([code], { type: 'text/javascript' });
+		const borogove = await import(URL.createObjectURL(blob));
+
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
+		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
+
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "srv1",
+			serverIdBy: "hatter@example.com",
+			localId: "loc1",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		const msg = builder.build();
+
+		await persistence.storeMessages("alice@example.com", [msg]);
+
+		const byServerId = await persistence.getMessage("alice@example.com", "hatter@example.com", "srv1", null);
+		const byLocalId = await persistence.getMessage("alice@example.com", "hatter@example.com", null, "loc1");
+
+		return {
+			byServerId: byServerId ? { serverId: byServerId.serverId, localId: byServerId.localId } : null,
+			byLocalId: byLocalId ? { serverId: byLocalId.serverId, localId: byLocalId.localId } : null
+		};
+	}, code);
+
+	expect(result.byServerId).not.toBeNull();
+	expect(result.byServerId.serverId).toBe("srv1");
+	expect(result.byLocalId).not.toBeNull();
+	expect(result.byLocalId.localId).toBe("loc1");
+});
+
+test("storeReaction", async ({ page }) => {
+	page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" }));
+	const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8");
+	await page.goto("https://localhost/");
+	const result = await page.evaluate(async (code) => {
+		const blob = new Blob([code], { type: 'text/javascript' });
+		const borogove = await import(URL.createObjectURL(blob));
+
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
+		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
+
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "srv1",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		await persistence.storeMessages("alice@example.com", [builder.build()]);
+
+		const reaction = new borogove.Reaction("alice@example.com", "2020-01-01T00:00:01Z", "👍");
+		const update = new borogove.ReactionUpdate("up1", "srv1", "hatter@example.com", null, "hatter@example.com", "alice@example.com", "2020-01-01T00:00:01Z", [reaction], borogove.ReactionUpdateKind.EmojiReactions);
+
+		const msg = await persistence.storeReaction("alice@example.com", update);
+		return { reactions: [...msg.reactions.entries()].map(([k, v]) => ({ key: k, count: v.length })) };
+	}, code);
+
+	expect(result.reactions.length).toBe(1);
+	expect(result.reactions[0].key).toBe("👍");
+	expect(result.reactions[0].count).toBe(1);
+});
+
+test("updateMessageStatus", async ({ page }) => {
+	page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" }));
+	const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8");
+	await page.goto("https://localhost/");
+	const result = await page.evaluate(async (code) => {
+		const blob = new Blob([code], { type: 'text/javascript' });
+		const borogove = await import(URL.createObjectURL(blob));
+
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
+		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
+
+		const builder = new borogove.ChatMessageBuilder({
+			localId: "loc1",
+			senderId: "alice@example.com",
+			direction: 1, // MessageSent
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("hatter@example.com");
+		builder.from = borogove.JID.parse("alice@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		await persistence.storeMessages("alice@example.com", [builder.build()]);
+
+		const updated = await persistence.updateMessageStatus("alice@example.com", "loc1", 1, "Delivered"); // MessageDelivered
+		return { status: updated.status, statusText: updated.statusText };
+	}, code);
+
+	expect(result.status).toBe(1);
+	expect(result.statusText).toBe("Delivered");
+});
+
+test("searchMessages", async ({ page }) => {
+	page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" }));
+	const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8");
+	await page.goto("https://localhost/");
+	const result = await page.evaluate(async (code) => {
+		const blob = new Blob([code], { type: 'text/javascript' });
+		const borogove = await import(URL.createObjectURL(blob));
+
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
+		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
+
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "srv1",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.text = "Hello world";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "srv2",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder2.sortId = "a1";
+		builder2.text = "Goodbye world";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("hatter@example.com");
+		builder2.recipients = [builder2.to];
+		builder2.replyTo = [builder2.from];
+
+		await persistence.storeMessages("alice@example.com", [builder.build(), builder2.build()]);
+
+		const results = await persistence.searchMessages("alice@example.com", "hatter@example.com", "hello");
+		return results.map(m => m.text);
+	}, code);
+
+	expect(result.length).toBe(1);
+	expect(result[0]).toBe("Hello world");
+});
+
+test("removeAccount and listAccounts", async ({ page }) => {
+	page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" }));
+	const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8");
+	await page.goto("https://localhost/");
+	const result = await page.evaluate(async (code) => {
+		const blob = new Blob([code], { type: 'text/javascript' });
+		const borogove = await import(URL.createObjectURL(blob));
+
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
+		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
+
+		await persistence.storeLogin("alice@example.com", "client1", "Alice", null);
+		await persistence.storeLogin("bob@example.com", "client2", "Bob", null);
+
+		const accountsBefore = await persistence.listAccounts();
+		await persistence.removeAccount("alice@example.com", true);
+		const accountsAfter = await persistence.listAccounts();
+
+		return { accountsBefore, accountsAfter };
+	}, code);
+
+	expect(result.accountsBefore).toContain("alice@example.com");
+	expect(result.accountsBefore).toContain("bob@example.com");
+	expect(result.accountsAfter).not.toContain("alice@example.com");
+	expect(result.accountsAfter).toContain("bob@example.com");
+});
+
+test("getChatUnreadDetails", async ({ page }) => {
+	page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" }));
+	const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8");
+	await page.goto("https://localhost/");
+	const result = await page.evaluate(async (code) => {
+		const blob = new Blob([code], { type: 'text/javascript' });
+		const borogove = await import(URL.createObjectURL(blob));
+
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
+		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
+
+		const chat = Object.create(borogove.DirectChat.prototype);
+		chat.chatId = "hatter@example.com";
+		chat.readUpToId = "srv1";
+		chat.notificationsFiltered = () => false;
+
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "srv1",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "srv2",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder2.sortId = "a1";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("hatter@example.com");
+		builder2.recipients = [builder2.to];
+		builder2.replyTo = [builder2.from];
+
+		await persistence.storeMessages("alice@example.com", [builder.build(), builder2.build()]);
+
+		return await persistence.getChatUnreadDetails("alice@example.com", chat);
+	}, code);
+
+	expect(result.unreadCount).toBe(1);
+	expect(result.message.serverId).toBe("srv2");
+});
+
+test("media storage functions", async ({ page }) => {
+	page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" }));
+	const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8");
+	await page.goto("https://localhost/");
+	const result = await page.evaluate(async (code) => {
+		const blob = new Blob([code], { type: 'text/javascript' });
+		const borogove = await import(URL.createObjectURL(blob));
+
+		const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
+		const persistence = await borogove.persistence.IDB("snikket", mediaStore);
+
+		const buffer = new Uint8Array([1, 2, 3]).buffer;
+		await persistence.storeMedia("image/png", buffer);
+		const sha256 = await crypto.subtle.digest("SHA-256", buffer);
+		const hasBefore = await persistence.hasMedia("sha-256", sha256);
+		await persistence.removeMedia("sha-256", sha256);
+		const hasAfter = await persistence.hasMedia("sha-256", sha256);
+
+		return { hasBefore, hasAfter };
+	}, code);
+
+	expect(result.hasBefore).toBe(true);
+	expect(result.hasAfter).toBe(false);
+});