Refine domain, integrate into app
tests / tests (push) Failing after 5m14s

This commit is contained in:
Jon Staab
2026-06-19 12:50:34 -07:00
parent 1bd62d3024
commit e2a6ef21cd
115 changed files with 1354 additions and 3176 deletions
@@ -59,7 +59,7 @@ describe("BlockedRelayList", () => {
it("builds from a fresh builder and normalizes urls", async () => {
const tmpl = await new BlockedRelayListBuilder()
.addRelay("wss://relay.one.example")
.addUrl("wss://relay.one.example")
.toTemplate(signer)
expect(getTagValues("relay", tmpl.tags)).toEqual([normalizeRelayUrl("wss://relay.one.example")])
@@ -69,14 +69,14 @@ describe("BlockedRelayList", () => {
const event = makeEvent({tags: [["relay", r1]]})
const list = await BlockedRelayList.fromEvent(event)
const tmpl = await list.builder().setRelays([r2, r3]).toTemplate(signer)
const tmpl = await list.builder().setUrls([r2, r3]).toTemplate(signer)
expect(getTagValues("relay", tmpl.tags).sort()).toEqual([r2, r3].sort())
})
it("round-trips public and private entries through encryption", async () => {
const event = await new BlockedRelayListBuilder()
.addRelay(r1)
.addUrl(r1)
.addPrivate(["relay", r2])
.toEvent(signer)
@@ -37,7 +37,7 @@ describe("BlossomServerList", () => {
const list = await BlossomServerList.fromEvent(event)
expect(list.servers().sort()).toEqual([norm(s1), norm(s2)].sort())
expect(list.urls().sort()).toEqual([norm(s1), norm(s2)].sort())
expect(list.includes(s1)).toBe(true)
expect(list.includes(s3)).toBe(false)
})
@@ -60,7 +60,7 @@ describe("BlossomServerList", () => {
})
it("builds from a fresh builder and normalizes urls", async () => {
const tmpl = await new BlossomServerListBuilder().addServer(s1).toTemplate(signer)
const tmpl = await new BlossomServerListBuilder().addUrl(s1).toTemplate(signer)
expect(getTagValues("server", tmpl.tags)).toEqual([norm(s1)])
})
@@ -69,14 +69,14 @@ describe("BlossomServerList", () => {
const event = makeEvent({tags: [["server", s1]]})
const list = await BlossomServerList.fromEvent(event)
const tmpl = await list.builder().setServers([s2, s3]).toTemplate(signer)
const tmpl = await list.builder().setUrls([s2, s3]).toTemplate(signer)
expect(getTagValues("server", tmpl.tags).sort()).toEqual([norm(s2), norm(s3)].sort())
})
it("round-trips public and private entries through encryption", async () => {
const event = await new BlossomServerListBuilder()
.addServer(s1)
.addUrl(s1)
.addPrivate(["server", norm(s2)])
.toEvent(signer)
@@ -86,12 +86,12 @@ describe("BlossomServerList", () => {
const decrypted = await BlossomServerList.fromEvent(event, signer)
expect(decrypted.decrypted).toBe(true)
expect(decrypted.servers().sort()).toEqual([norm(s1), norm(s2)].sort())
expect(decrypted.urls().sort()).toEqual([norm(s1), norm(s2)].sort())
const publicOnly = await BlossomServerList.fromEvent(event)
expect(publicOnly.decrypted).toBe(false)
expect(publicOnly.servers()).toEqual([norm(s1)])
expect(publicOnly.urls()).toEqual([norm(s1)])
})
it("throws on the wrong kind", async () => {
+5 -4
View File
@@ -41,7 +41,7 @@ describe("Classified", () => {
expect(c.identifier()).toBe("abc")
expect(c.title()).toBe("Bike")
expect(c.summary()).toBe("A good bike")
expect(c.price()).toEqual({amount: 100, currency: "USD"})
expect(c.price()).toEqual({amount: 100, currency: "USD", frequency: ""})
expect(c.status()).toBe("active")
expect(c.images()).toEqual(["https://example.com/a.jpg", "https://example.com/b.jpg"])
expect(c.topics()).toEqual(["cycling"])
@@ -51,7 +51,7 @@ describe("Classified", () => {
it("defaults the price currency to SAT", async () => {
const c = await Classified.fromEvent(makeEvent({tags: [["d", "x"], ["price", "50"]]}))
expect(c.price()).toEqual({amount: 50, currency: "SAT"})
expect(c.price()).toEqual({amount: 50, currency: "SAT", frequency: ""})
})
it("round-trips with no duplicate represented tags", async () => {
@@ -84,8 +84,9 @@ describe("Classified", () => {
expect(tmpl.content).toBe("for sale")
})
it("builds from a fresh builder with an auto-generated d", async () => {
it("builds from a fresh builder", async () => {
const tmpl = await new ClassifiedBuilder()
.setIdentifier("listing1")
.setTitle("Fresh")
.setContent("desc")
.setPrice(25)
@@ -94,7 +95,7 @@ describe("Classified", () => {
.toTemplate(signer)
expect(tmpl.kind).toBe(CLASSIFIED)
expect(tmpl.tags.find(t => t[0] === "d")?.[1]).toBeTruthy()
expect(tmpl.tags).toContainEqual(["d", "listing1"])
expect(tmpl.tags).toContainEqual(["title", "Fresh"])
expect(tmpl.tags).toContainEqual(["price", "25", "SAT"])
expect(tmpl.tags).toContainEqual(["image", "https://example.com/c.jpg"])
@@ -41,12 +41,6 @@ describe("Comment", () => {
const comment = await Comment.fromEvent(event)
expect(comment.content()).toBe("nice thread")
expect(comment.rootId()).toBe(rootId)
expect(comment.rootKind()).toBe("11")
expect(comment.rootPubkey()).toBe(rootPubkey)
expect(comment.parentId()).toBe(parentId)
expect(comment.parentKind()).toBe("1111")
expect(comment.parentPubkey()).toBe(parentPubkey)
expect(comment.root()).toEqual({id: rootId, address: undefined, kind: "11", pubkey: rootPubkey})
expect(comment.parent()).toEqual({
id: parentId,
+3 -3
View File
@@ -32,7 +32,7 @@ describe("EmojiList", () => {
const list = await EmojiList.fromEvent(event)
expect(list.addresses()).toEqual([setAddress])
expect(list.emojiSets()).toEqual([setAddress])
expect(list.emojis()).toEqual([emojiTag])
})
@@ -81,12 +81,12 @@ describe("EmojiList", () => {
const decrypted = await EmojiList.fromEvent(event, signer)
expect(decrypted.decrypted).toBe(true)
expect(decrypted.addresses().sort()).toEqual([setAddress, setAddress2].sort())
expect(decrypted.emojiSets().sort()).toEqual([setAddress, setAddress2].sort())
const publicOnly = await EmojiList.fromEvent(event)
expect(publicOnly.decrypted).toBe(false)
expect(publicOnly.addresses()).toEqual([setAddress])
expect(publicOnly.emojiSets()).toEqual([setAddress])
})
it("preserves undecrypted ciphertext on pass-through", async () => {
+7 -8
View File
@@ -48,38 +48,37 @@ describe("Feed", () => {
["title", "My Feed"],
["description", "all the things"],
["feed", JSON.stringify(definition)],
// "alt" is consumed but not re-emitted, so it shouldn't survive.
["alt", "My Feed"],
// "alt" is a represented/derived tag (mirrors title), so use a distinct
// unknown key for the passthrough assertion.
["zzz", "x"],
],
})
const tmpl = await (await Feed.fromEvent(event)).builder().toTemplate(signer)
for (const key of ["d", "title", "description", "feed", "alt"]) {
for (const key of ["d", "title", "description", "feed"]) {
expect(tmpl.tags.filter(t => t[0] === key).length).toBe(1)
}
expect(tmpl.tags).toContainEqual(["d", "abc"])
expect(tmpl.tags).toContainEqual(["title", "My Feed"])
expect(tmpl.tags).toContainEqual(["feed", JSON.stringify(definition)])
// The derived "alt" tag mirrors the title.
expect(tmpl.tags).toContainEqual(["alt", "My Feed"])
// "alt" is consumed but not re-emitted.
expect(tmpl.tags.filter(t => t[0] === "alt")).toHaveLength(0)
// Unknown passthrough tag survives.
expect(tmpl.tags).toContainEqual(["zzz", "x"])
})
it("builds from a fresh builder with an auto-generated d", async () => {
it("builds from a fresh builder", async () => {
const tmpl = await new FeedBuilder()
.setIdentifier("feed1")
.setTitle("Fresh")
.setDescription("desc")
.setDefinition(definition)
.toTemplate(signer)
expect(tmpl.kind).toBe(FEED)
expect(tmpl.tags.find(t => t[0] === "d")?.[1]).toBeTruthy()
expect(tmpl.tags).toContainEqual(["d", "feed1"])
expect(tmpl.tags).toContainEqual(["title", "Fresh"])
expect(tmpl.tags).toContainEqual(["alt", "Fresh"])
expect(tmpl.tags).toContainEqual(["description", "desc"])
expect(tmpl.tags).toContainEqual(["feed", JSON.stringify(definition)])
})
+21 -5
View File
@@ -25,7 +25,7 @@ describe("Handler", () => {
content: JSON.stringify({
name: "Coracle",
about: "a client",
image: "https://example.com/i.png",
picture: "https://example.com/i.png",
website: "https://example.com",
lud16: "a@example.com",
nip05: "a@example.com",
@@ -43,22 +43,36 @@ describe("Handler", () => {
expect(handler.values.name).toBe("Coracle")
expect(handler.name()).toBe("Coracle")
expect(handler.about()).toBe("a client")
expect(handler.image()).toBe("https://example.com/i.png")
expect(handler.picture()).toBe("https://example.com/i.png")
expect(handler.website()).toBe("https://example.com")
expect(handler.lud16()).toBe("a@example.com")
expect(handler.nip05()).toBe("a@example.com")
expect(handler.kinds()).toEqual([1, 30023])
})
it("maps display_name and picture aliases to canonical accessors", async () => {
it("preserves unknown content metadata on round-trip", async () => {
const event = makeEvent({
content: JSON.stringify({name: "Coracle", custom_field: "keep me"}),
tags: [["d", "myhandler"]],
})
const tmpl = await (await Handler.fromEvent(event)).builder().toTemplate(signer)
const parsed = JSON.parse(tmpl.content)
expect(parsed.name).toBe("Coracle")
expect(parsed.custom_field).toBe("keep me")
})
it("ignores non-spec aliases like display_name", async () => {
const handler = await Handler.fromEvent(
makeEvent({
content: JSON.stringify({display_name: "Alias", picture: "https://example.com/p.png"}),
}),
)
expect(handler.name()).toBe("Alias")
expect(handler.image()).toBe("https://example.com/p.png")
// NIP-89 metadata follows NIP-01: only the canonical `name`/`picture` fields are read.
expect(handler.name()).toBeUndefined()
expect(handler.picture()).toBe("https://example.com/p.png")
})
it("round-trips with no duplication", async () => {
@@ -87,6 +101,7 @@ describe("Handler", () => {
it("builds from a fresh builder", async () => {
const tmpl = await new HandlerBuilder()
.setIdentifier("myhandler")
.setName("MyApp")
.setAbout("does things")
.setWebsite("https://my.app")
@@ -94,6 +109,7 @@ describe("Handler", () => {
.toTemplate(signer)
expect(tmpl.kind).toBe(HANDLER_INFORMATION)
expect(tmpl.tags).toContainEqual(["d", "myhandler"])
const parsed = JSON.parse(tmpl.content)
expect(parsed.name).toBe("MyApp")
expect(parsed.about).toBe("does things")
@@ -73,9 +73,8 @@ describe("HandlerRecommendation", () => {
it("builds from a fresh builder", async () => {
const builder = new HandlerRecommendationBuilder()
// The d identifier holds the recommended kind; it has no setter, so set it
// directly on the public field.
builder.identifier = "1"
// The d identifier holds the recommended kind.
builder.setIdentifier("1")
const tmpl = await builder
.addRecommendation(webAddress, "wss://relay.one", "web")
@@ -57,7 +57,7 @@ describe("MessagingRelayList", () => {
it("builds from a fresh builder and normalizes urls", async () => {
const tmpl = await new MessagingRelayListBuilder()
.addRelay("wss://inbox.one.example")
.addUrl("wss://inbox.one.example")
.toTemplate(signer)
expect(getTagValues("relay", tmpl.tags)).toEqual([normalizeRelayUrl("wss://inbox.one.example")])
@@ -67,14 +67,14 @@ describe("MessagingRelayList", () => {
const event = makeEvent({tags: [["relay", r1]]})
const list = await MessagingRelayList.fromEvent(event)
const tmpl = await list.builder().setRelays([r2, r3]).toTemplate(signer)
const tmpl = await list.builder().setUrls([r2, r3]).toTemplate(signer)
expect(getTagValues("relay", tmpl.tags).sort()).toEqual([r2, r3].sort())
})
it("round-trips public and private entries through encryption", async () => {
const event = await new MessagingRelayListBuilder()
.addRelay(r1)
.addUrl(r1)
.addPrivate(["relay", r2])
.toEvent(signer)
+2 -2
View File
@@ -43,7 +43,7 @@ describe("Poll", () => {
])
expect(poll.pollType()).toBe("multiplechoice")
expect(poll.endsAt()).toBe(1234)
expect(poll.relays()).toEqual(["wss://relay.one", "wss://relay.two"])
expect(poll.urls()).toEqual(["wss://relay.one", "wss://relay.two"])
})
it("tallies results from response events", async () => {
@@ -102,7 +102,7 @@ describe("Poll", () => {
.addOption("Blue", "2")
.setPollType("multiplechoice")
.setEndsAt(9999)
.setRelays(["wss://relay.one"])
.setUrls(["wss://relay.one"])
.toTemplate(signer)
expect(tmpl.kind).toBe(POLL)
+1 -1
View File
@@ -51,7 +51,7 @@ describe("Profile", () => {
it("seeds the builder from the reader and edits values", async () => {
const profile = await Profile.fromEvent(makeEvent({content: JSON.stringify({name: "alice"})}))
const event = await profile.builder().about("hello").toEvent(signer)
const event = await profile.builder().setAbout("hello").toEvent(signer)
const updated = await Profile.fromEvent(event)
expect(updated.name()).toBe("alice")
@@ -1,13 +1,8 @@
import {describe, it, expect} from "vitest"
import {makeSecret, RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER, NOTE} from "@welshman/util"
import {makeSecret, RELAY_ADD_MEMBER, NOTE} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {Nip01Signer} from "@welshman/signer"
import {
RelayAddMember,
RelayAddMemberBuilder,
RelayRemoveMember,
RelayRemoveMemberBuilder,
} from "../src/kinds/RelayMembershipOp"
import {RelayAddMember, RelayAddMemberBuilder} from "../src/kinds/RelayAddMember"
const signer = new Nip01Signer(makeSecret())
const pubkey = "ee".repeat(32)
@@ -34,9 +29,7 @@ describe("RelayAddMember", () => {
})
it("round-trips with no duplicate p tags and passthrough", async () => {
const op = await RelayAddMember.fromEvent(
makeEvent({tags: [["p", a], ["p", b], ["alt", "x"]]}),
)
const op = await RelayAddMember.fromEvent(makeEvent({tags: [["p", a], ["p", b], ["alt", "x"]]}))
const tmpl = await op.builder().toTemplate(signer)
@@ -58,27 +51,3 @@ describe("RelayAddMember", () => {
await expect(RelayAddMember.fromEvent(makeEvent({kind: NOTE}))).rejects.toThrow()
})
})
describe("RelayRemoveMember", () => {
it("reads affected pubkeys with the remove kind", async () => {
const op = await RelayRemoveMember.fromEvent(
makeEvent({kind: RELAY_REMOVE_MEMBER, tags: [["p", a]]}),
)
expect(op.kind).toBe(RELAY_REMOVE_MEMBER)
expect(op.pubkeys()).toEqual([a])
})
it("builds fresh with the remove kind", async () => {
const tmpl = await new RelayRemoveMemberBuilder().addPubkey(a).toTemplate(signer)
expect(tmpl.kind).toBe(RELAY_REMOVE_MEMBER)
expect(tmpl.tags).toContainEqual(["p", a])
})
it("throws when the add kind is read as a remove", async () => {
await expect(
RelayRemoveMember.fromEvent(makeEvent({kind: RELAY_ADD_MEMBER})),
).rejects.toThrow()
})
})
+1 -1
View File
@@ -35,7 +35,7 @@ describe("RelayLeave", () => {
})
it("sets the group via a fresh builder", async () => {
const tmpl = await new RelayLeaveBuilder().group(group).toTemplate(signer)
const tmpl = await new RelayLeaveBuilder().setGroup(group).toTemplate(signer)
expect(tmpl.tags).toContainEqual(["h", group])
})
+7 -7
View File
@@ -62,10 +62,10 @@ describe("RelayList", () => {
it("adds modeless and single-mode relays via a fresh builder", async () => {
const tmpl = await new RelayListBuilder()
.addRelay(read, RelayMode.Read)
.addRelay(write, RelayMode.Write)
.addRelay(both, RelayMode.Read)
.addRelay(both, RelayMode.Write)
.addUrl(read, RelayMode.Read)
.addUrl(write, RelayMode.Write)
.addUrl(both, RelayMode.Read)
.addUrl(both, RelayMode.Write)
.toTemplate(signer)
expect(tmpl.kind).toBe(RELAYS)
@@ -77,9 +77,9 @@ describe("RelayList", () => {
it("downgrades a modeless relay when one mode is removed", async () => {
const tmpl = await new RelayListBuilder()
.addRelay(both, RelayMode.Read)
.addRelay(both, RelayMode.Write)
.removeRelay(both, RelayMode.Read)
.addUrl(both, RelayMode.Read)
.addUrl(both, RelayMode.Write)
.removeUrl(both, RelayMode.Read)
.toTemplate(signer)
expect(tmpl.tags).toContainEqual(["r", both, RelayMode.Write])
@@ -0,0 +1,41 @@
import {describe, it, expect} from "vitest"
import {makeSecret, RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {Nip01Signer} from "@welshman/signer"
import {RelayRemoveMember, RelayRemoveMemberBuilder} from "../src/kinds/RelayRemoveMember"
const signer = new Nip01Signer(makeSecret())
const pubkey = "ee".repeat(32)
const a = "aa".repeat(32)
const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
({
id: "ff".repeat(32),
pubkey,
created_at: 0,
kind: RELAY_REMOVE_MEMBER,
tags: [],
content: "",
sig: "00".repeat(64),
...overrides,
}) as TrustedEvent
describe("RelayRemoveMember", () => {
it("reads affected pubkeys with the remove kind", async () => {
const op = await RelayRemoveMember.fromEvent(makeEvent({tags: [["p", a]]}))
expect(op.kind).toBe(RELAY_REMOVE_MEMBER)
expect(op.pubkeys()).toEqual([a])
})
it("builds fresh with the remove kind", async () => {
const tmpl = await new RelayRemoveMemberBuilder().addPubkey(a).toTemplate(signer)
expect(tmpl.kind).toBe(RELAY_REMOVE_MEMBER)
expect(tmpl.tags).toContainEqual(["p", a])
})
it("throws when the add kind is read as a remove", async () => {
await expect(RelayRemoveMember.fromEvent(makeEvent({kind: RELAY_ADD_MEMBER}))).rejects.toThrow()
})
})
+8 -8
View File
@@ -74,15 +74,15 @@ describe("RelaySet", () => {
expect(tmpl.tags).toContainEqual(["alt", "x"])
})
it("auto-generates a d identifier on a fresh builder", async () => {
const builder = new RelaySetBuilder()
expect(builder.identifier).toBeTruthy()
const tmpl = await builder.setTitle("Fresh").addRelay(relayA).toTemplate(signer)
it("builds from a fresh builder", async () => {
const tmpl = await new RelaySetBuilder()
.setIdentifier("my-set")
.setTitle("Fresh")
.addUrl(relayA)
.toTemplate(signer)
expect(tmpl.kind).toBe(NAMED_RELAYS)
expect(tmpl.tags.filter(t => t[0] === "d").length).toBe(1)
expect(tmpl.tags).toContainEqual(["d", "my-set"])
expect(tmpl.tags).toContainEqual(["title", "Fresh"])
expect(tmpl.tags).toContainEqual(["relay", normalizeRelayUrl(relayA)])
})
@@ -92,7 +92,7 @@ describe("RelaySet", () => {
makeEvent({tags: [["d", "my-set"], ["title", "My Set"], ["relay", relayA]]}),
)
const tmpl = await reader.builder().setRelays([relayB]).toTemplate(signer)
const tmpl = await reader.builder().setUrls([relayB]).toTemplate(signer)
expect(tmpl.tags).toContainEqual(["relay", normalizeRelayUrl(relayB)])
expect(tmpl.tags.some(t => t[0] === "relay" && t[1] === normalizeRelayUrl(relayA))).toBe(false)
@@ -1,13 +1,8 @@
import {describe, it, expect} from "vitest"
import {makeSecret, ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER, NOTE, getTagValue} from "@welshman/util"
import {makeSecret, ROOM_ADD_MEMBER, NOTE, getTagValue} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {Nip01Signer} from "@welshman/signer"
import {
RoomAddMember,
RoomAddMemberBuilder,
RoomRemoveMember,
RoomRemoveMemberBuilder,
} from "../src/kinds/RoomMembershipOp"
import {RoomAddMember, RoomAddMemberBuilder} from "../src/kinds/RoomAddMember"
const signer = new Nip01Signer(makeSecret())
const pubkey = "ee".repeat(32)
@@ -57,7 +52,7 @@ describe("RoomAddMember", () => {
it("builds from a fresh builder", async () => {
const tmpl = await new RoomAddMemberBuilder()
.group("room2")
.setGroup("room2")
.addPubkey(a)
.addPubkey(a) // dedup
.addPubkey(b)
@@ -74,41 +69,3 @@ describe("RoomAddMember", () => {
await expect(RoomAddMember.fromEvent(makeEvent({kind: NOTE}))).rejects.toThrow()
})
})
describe("RoomRemoveMember", () => {
it("uses the remove kind and reads pubkeys", async () => {
const op = await RoomRemoveMember.fromEvent(
makeEvent({kind: ROOM_REMOVE_MEMBER, tags: [["h", "room1"], ["p", a]]}),
)
expect(op.kind).toBe(ROOM_REMOVE_MEMBER)
expect(op.pubkeys()).toEqual([a])
})
it("round-trips through the remove builder", async () => {
const op = await RoomRemoveMember.fromEvent(
makeEvent({kind: ROOM_REMOVE_MEMBER, tags: [["h", "room1"], ["p", a], ["p", b]]}),
)
const tmpl = await op.builder().toTemplate(signer)
expect(tmpl.kind).toBe(ROOM_REMOVE_MEMBER)
expect(tmpl.tags.filter(t => t[0] === "h").length).toBe(1)
expect(tmpl.tags.filter(t => t[0] === "p").length).toBe(2)
expect(getTagValue("h", tmpl.tags)).toBe("room1")
})
it("builds from a fresh remove builder", async () => {
const tmpl = await new RoomRemoveMemberBuilder().group("room2").addPubkey(a).toTemplate(signer)
expect(tmpl.kind).toBe(ROOM_REMOVE_MEMBER)
expect(getTagValue("h", tmpl.tags)).toBe("room2")
expect(tmpl.tags).toContainEqual(["p", a])
})
it("throws on the wrong kind", async () => {
await expect(
RoomRemoveMember.fromEvent(makeEvent({kind: ROOM_ADD_MEMBER, tags: [["p", a]]})),
).rejects.toThrow()
})
})
+4 -5
View File
@@ -27,7 +27,6 @@ describe("RoomAdmins", () => {
makeEvent({tags: [["d", "room1"], ["p", a], ["p", b], ["alt", "x"]]}),
)
expect(room.h()).toBe("room1")
expect(room.identifier()).toBe("room1")
expect(room.pubkeys()).toEqual([a, b])
})
@@ -51,10 +50,10 @@ describe("RoomAdmins", () => {
it("builds from a fresh builder", async () => {
const tmpl = await new RoomAdminsBuilder()
.setH("room2")
.addPubkey(a)
.addPubkey(a) // dedup
.addPubkey(b)
.setIdentifier("room2")
.addAdmin(a)
.addAdmin(a) // dedup
.addAdmin(b)
.toTemplate(signer)
expect(tmpl.tags).toContainEqual(["d", "room2"])
+1 -1
View File
@@ -35,7 +35,7 @@ describe("RoomCreate", () => {
})
it("sets the group via a fresh builder", async () => {
const tmpl = await new RoomCreateBuilder().group(group).toTemplate(signer)
const tmpl = await new RoomCreateBuilder().setGroup(group).toTemplate(signer)
expect(tmpl.tags).toContainEqual(["h", group])
})
+12 -27
View File
@@ -6,6 +6,7 @@ import {RoomDelete, RoomDeleteBuilder} from "../src/kinds/RoomDelete"
const signer = new Nip01Signer(makeSecret())
const pubkey = "ee".repeat(32)
const group = "room1"
const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
({
@@ -20,46 +21,30 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
}) as TrustedEvent
describe("RoomDelete", () => {
it("reads multiple repeatable h tags", async () => {
const del = await RoomDelete.fromEvent(
makeEvent({tags: [["h", "room1"], ["h", "room2"], ["h", "room3"], ["alt", "x"]]}),
)
it("reads the target room via group()", async () => {
const del = await RoomDelete.fromEvent(makeEvent({tags: [["h", group]]}))
expect(del.hs()).toEqual(["room1", "room2", "room3"])
expect(del.h()).toBe("room1")
expect(del.group()).toBe(group)
})
it("round-trips multiple rooms, emitting one h each with no duplication", async () => {
const del = await RoomDelete.fromEvent(
makeEvent({tags: [["h", "room1"], ["h", "room2"], ["h", "room3"], ["alt", "x"]]}),
)
it("round-trips the group behavior tag without duplication", async () => {
const del = await RoomDelete.fromEvent(makeEvent({tags: [["h", group], ["alt", "x"]]}))
const tmpl = await del.builder().toTemplate(signer)
expect(tmpl.kind).toBe(ROOM_DELETE)
// Three rooms in, three h tags out — exactly one per room, no duplicates.
expect(tmpl.tags.filter(t => t[0] === "h").length).toBe(3)
expect(tmpl.tags).toContainEqual(["h", "room1"])
expect(tmpl.tags).toContainEqual(["h", "room2"])
expect(tmpl.tags).toContainEqual(["h", "room3"])
// Unknown passthrough tag survives.
expect(tmpl.tags.filter(t => t[0] === "h").length).toBe(1)
expect(tmpl.tags).toContainEqual(["h", group])
expect(tmpl.tags).toContainEqual(["alt", "x"])
})
it("builds from a fresh builder and edits the room set", async () => {
const tmpl = await new RoomDeleteBuilder()
.addRoom("room1")
.addRoom("room1") // dedup
.addRoom("room2")
.removeRoom("room1")
.toTemplate(signer)
it("sets the target room via a fresh builder", async () => {
const tmpl = await new RoomDeleteBuilder().setGroup(group).toTemplate(signer)
expect(tmpl.tags.filter(t => t[0] === "h").length).toBe(1)
expect(tmpl.tags).toContainEqual(["h", "room2"])
expect(tmpl.tags).not.toContainEqual(["h", "room1"])
expect(tmpl.tags).toContainEqual(["h", group])
})
it("requires at least one h tag", async () => {
it("requires an h group", async () => {
await expect(new RoomDeleteBuilder().toTemplate(signer)).rejects.toThrow()
})
+1 -1
View File
@@ -57,7 +57,7 @@ describe("RoomJoin", () => {
it("builds from a fresh builder", async () => {
const tmpl = await new RoomJoinBuilder()
.group("room2")
.setGroup("room2")
.setCode("xyz")
.setReason("hi there")
.toTemplate(signer)
+2 -3
View File
@@ -21,11 +21,10 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
}) as TrustedEvent
describe("RoomLeave", () => {
it("reads the group via group() and h()", async () => {
it("reads the group via group()", async () => {
const leave = await RoomLeave.fromEvent(makeEvent({tags: [["h", group]]}))
expect(leave.group()).toBe(group)
expect(leave.h()).toBe(group)
})
it("round-trips the group behavior tag without duplication", async () => {
@@ -40,7 +39,7 @@ describe("RoomLeave", () => {
})
it("sets the group via a fresh builder", async () => {
const tmpl = await new RoomLeaveBuilder().group(group).toTemplate(signer)
const tmpl = await new RoomLeaveBuilder().setGroup(group).toTemplate(signer)
expect(tmpl.tags).toContainEqual(["h", group])
})
@@ -28,7 +28,7 @@ describe("RoomMembers", () => {
makeEvent({tags: [["d", "room1"], ["p", a], ["p", b], ["alt", "x"]]}),
)
expect(room.h()).toBe("room1")
expect(room.identifier()).toBe("room1")
expect(room.members()).toEqual([a, b])
expect(room.isMember(a)).toBe(true)
expect(room.isMember(c)).toBe(false)
@@ -51,13 +51,9 @@ describe("RoomMembers", () => {
expect(tmpl.tags).toContainEqual(["alt", "x"])
})
it("requires an h identifier on a fresh builder", async () => {
await expect(new RoomMembersBuilder().addMember(a).toTemplate(signer)).rejects.toThrow()
})
it("builds from a fresh builder and edits membership", async () => {
const builder = new RoomMembersBuilder()
builder.h = "room2"
builder.setIdentifier("room2")
const tmpl = await builder
.addMember(a)
+3 -4
View File
@@ -36,7 +36,6 @@ describe("RoomMeta", () => {
}),
)
expect(room.h()).toBe("room1")
expect(room.identifier()).toBe("room1")
expect(room.name()).toBe("My Room")
expect(room.about()).toBe("a place")
@@ -46,7 +45,7 @@ describe("RoomMeta", () => {
expect(room.isHidden()).toBe(false)
expect(room.isPrivate()).toBe(true)
expect(room.isRestricted()).toBe(false)
expect(room.livekit()).toBe(true)
expect(room.hasLivekit()).toBe(true)
})
it("round-trips with no duplicated tags", async () => {
@@ -82,6 +81,7 @@ describe("RoomMeta", () => {
it("builds from a fresh builder", async () => {
const tmpl = await new RoomMetaBuilder()
.setIdentifier("room2")
.setName("Fresh")
.setAbout("desc")
.setPicture("https://pic", ["100x100"])
@@ -90,8 +90,7 @@ describe("RoomMeta", () => {
expect(tmpl.tags).toContainEqual(["name", "Fresh"])
expect(tmpl.tags).toContainEqual(["about", "desc"])
expect(tmpl.tags).toContainEqual(["picture", "https://pic", "100x100"])
// A d-identifier is auto-generated.
expect(tmpl.tags.find(t => t[0] === "d")?.[1]).toBeTruthy()
expect(tmpl.tags).toContainEqual(["d", "room2"])
})
it("throws on the wrong kind", async () => {
@@ -0,0 +1,58 @@
import {describe, it, expect} from "vitest"
import {makeSecret, ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER, getTagValue} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {Nip01Signer} from "@welshman/signer"
import {RoomRemoveMember, RoomRemoveMemberBuilder} from "../src/kinds/RoomRemoveMember"
const signer = new Nip01Signer(makeSecret())
const pubkey = "ee".repeat(32)
const a = "aa".repeat(32)
const b = "bb".repeat(32)
const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
({
id: "ff".repeat(32),
pubkey,
created_at: 0,
kind: ROOM_REMOVE_MEMBER,
tags: [],
content: "",
sig: "00".repeat(64),
...overrides,
}) as TrustedEvent
describe("RoomRemoveMember", () => {
it("uses the remove kind and reads pubkeys", async () => {
const op = await RoomRemoveMember.fromEvent(makeEvent({tags: [["h", "room1"], ["p", a]]}))
expect(op.kind).toBe(ROOM_REMOVE_MEMBER)
expect(op.pubkeys()).toEqual([a])
})
it("round-trips through the remove builder", async () => {
const op = await RoomRemoveMember.fromEvent(
makeEvent({tags: [["h", "room1"], ["p", a], ["p", b]]}),
)
const tmpl = await op.builder().toTemplate(signer)
expect(tmpl.kind).toBe(ROOM_REMOVE_MEMBER)
expect(tmpl.tags.filter(t => t[0] === "h").length).toBe(1)
expect(tmpl.tags.filter(t => t[0] === "p").length).toBe(2)
expect(getTagValue("h", tmpl.tags)).toBe("room1")
})
it("builds from a fresh remove builder", async () => {
const tmpl = await new RoomRemoveMemberBuilder().setGroup("room2").addPubkey(a).toTemplate(signer)
expect(tmpl.kind).toBe(ROOM_REMOVE_MEMBER)
expect(getTagValue("h", tmpl.tags)).toBe("room2")
expect(tmpl.tags).toContainEqual(["p", a])
})
it("throws on the wrong kind", async () => {
await expect(
RoomRemoveMember.fromEvent(makeEvent({kind: ROOM_ADD_MEMBER, tags: [["p", a]]})),
).rejects.toThrow()
})
})
@@ -46,9 +46,9 @@ describe("SearchRelayList", () => {
it("adds and removes relays via a fresh builder", async () => {
const tmpl = await new SearchRelayListBuilder()
.addRelay(relayA)
.addRelay(relayB)
.removeRelay(relayA)
.addUrl(relayA)
.addUrl(relayB)
.removeUrl(relayA)
.toTemplate(signer)
expect(tmpl.kind).toBe(SEARCH_RELAYS)
@@ -58,7 +58,7 @@ describe("SearchRelayList", () => {
it("round-trips public and private relays through encryption", async () => {
const event = await new SearchRelayListBuilder()
.addRelay(relayA)
.addUrl(relayA)
.addPrivate(["relay", relayB])
.toEvent(signer)
+1 -1
View File
@@ -72,7 +72,7 @@ describe("Thread", () => {
const tmpl = await new ThreadBuilder()
.setTitle("New thread")
.setContent("body")
.group("room")
.setGroup("room")
.toTemplate(signer)
expect(tmpl.kind).toBe(THREAD)
+3 -9
View File
@@ -48,13 +48,6 @@ describe("TimeEvent", () => {
expect(time.content()).toBe("meetup")
})
it("falls back to the legacy name tag for title", async () => {
const event = makeEvent({tags: [["name", "Legacy"]]})
const time = await TimeEvent.fromEvent(event)
expect(time.title()).toBe("Legacy")
})
it("round-trips with no duplicate represented tags", async () => {
const event = makeEvent({
content: "meetup",
@@ -101,15 +94,16 @@ describe("TimeEvent", () => {
expect(tmpl.tags).not.toContainEqual(["D", "999999"])
})
it("builds from a fresh builder with an auto-generated d", async () => {
it("builds from a fresh builder", async () => {
const tmpl = await new TimeEventBuilder()
.setIdentifier("event1")
.setTitle("Fresh")
.setStart(start)
.setEnd(end)
.toTemplate(signer)
expect(tmpl.kind).toBe(EVENT_TIME)
expect(tmpl.tags.find(t => t[0] === "d")?.[1]).toBeTruthy()
expect(tmpl.tags).toContainEqual(["d", "event1"])
expect(tmpl.tags).toContainEqual(["title", "Fresh"])
expect(tmpl.tags).toContainEqual(["start", String(start)])
})
+2 -2
View File
@@ -37,7 +37,7 @@ describe("ZapGoal", () => {
expect(goal.title()).toBe("New server fund")
expect(goal.summary()).toBe("help us buy a server")
expect(goal.amount()).toBe(500000)
expect(goal.relays()).toEqual(["wss://relay.one", "wss://relay.two"])
expect(goal.urls()).toEqual(["wss://relay.one", "wss://relay.two"])
})
it("defaults amount to 0 when missing or unparseable", async () => {
@@ -71,7 +71,7 @@ describe("ZapGoal", () => {
.setTitle("Goal")
.setSummary("a summary")
.setAmount(1000)
.setRelays(["wss://relay.one"])
.setUrls(["wss://relay.one"])
.toTemplate(signer)
expect(tmpl.kind).toBe(ZAP_GOAL)
+2 -8
View File
@@ -31,7 +31,6 @@ describe("ZapRequest", () => {
["p", recipient],
["e", eventId],
["relays", "wss://relay.one", "wss://relay.two"],
["anon"],
["alt", "x"],
],
})
@@ -42,8 +41,7 @@ describe("ZapRequest", () => {
expect(req.lnurl()).toBe("lnurl1xyz")
expect(req.recipient()).toBe(recipient)
expect(req.eventId()).toBe(eventId)
expect(req.relays()).toEqual(["wss://relay.one", "wss://relay.two"])
expect(req.isAnonymous()).toBe(true)
expect(req.urls()).toEqual(["wss://relay.one", "wss://relay.two"])
expect(req.comment()).toBe("thanks!")
})
@@ -56,7 +54,6 @@ describe("ZapRequest", () => {
["p", recipient],
["e", eventId],
["relays", "wss://relay.one"],
["anon"],
["alt", "x"],
],
})
@@ -69,7 +66,6 @@ describe("ZapRequest", () => {
expect(tmpl.tags.filter(t => t[0] === "p").length).toBe(1)
expect(tmpl.tags.filter(t => t[0] === "e").length).toBe(1)
expect(tmpl.tags.filter(t => t[0] === "relays").length).toBe(1)
expect(tmpl.tags.filter(t => t[0] === "anon").length).toBe(1)
expect(tmpl.tags).toContainEqual(["alt", "x"])
})
@@ -79,8 +75,7 @@ describe("ZapRequest", () => {
.setLnurl("lnurl1abc")
.setRecipient(recipient)
.setEventId(eventId)
.setRelays(["wss://relay.one"])
.setAnonymous()
.setUrls(["wss://relay.one"])
.setComment("hi")
.toTemplate(signer)
@@ -91,7 +86,6 @@ describe("ZapRequest", () => {
expect(tmpl.tags).toContainEqual(["p", recipient])
expect(tmpl.tags).toContainEqual(["e", eventId])
expect(tmpl.tags).toContainEqual(["relays", "wss://relay.one"])
expect(tmpl.tags).toContainEqual(["anon"])
})
it("throws on the wrong kind", async () => {