applesauce-core 0.0.0-next-20250429163257 → 0.0.0-next-20250522030625
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/exports.test.d.ts +1 -0
- package/dist/__tests__/exports.test.js +28 -0
- package/dist/__tests__/index.test.d.ts +1 -0
- package/dist/__tests__/index.test.js +17 -0
- package/dist/event-store/__tests__/event-store.test.js +6 -0
- package/dist/event-store/database.d.ts +3 -3
- package/dist/event-store/database.js +6 -3
- package/dist/event-store/event-store.d.ts +10 -6
- package/dist/event-store/event-store.js +16 -6
- package/dist/event-store/interface.d.ts +2 -2
- package/dist/helpers/__tests__/app-handlers.test.d.ts +1 -0
- package/dist/helpers/__tests__/app-handlers.test.js +184 -0
- package/dist/helpers/__tests__/emoji.test.js +96 -1
- package/dist/helpers/__tests__/exports.test.d.ts +1 -0
- package/dist/helpers/__tests__/exports.test.js +245 -0
- package/dist/helpers/__tests__/index.test.d.ts +1 -0
- package/dist/helpers/__tests__/index.test.js +220 -0
- package/dist/helpers/__tests__/messages.test.d.ts +1 -0
- package/dist/helpers/__tests__/messages.test.js +91 -0
- package/dist/helpers/__tests__/profile.test.d.ts +1 -0
- package/dist/helpers/__tests__/profile.test.js +72 -0
- package/dist/helpers/__tests__/reactions.test.d.ts +1 -0
- package/dist/helpers/__tests__/reactions.test.js +88 -0
- package/dist/helpers/app-handlers.d.ts +23 -0
- package/dist/helpers/app-handlers.js +68 -0
- package/dist/helpers/blossom.d.ts +2 -0
- package/dist/helpers/blossom.js +18 -0
- package/dist/helpers/emoji.d.ts +10 -2
- package/dist/helpers/emoji.js +21 -3
- package/dist/helpers/index.d.ts +4 -0
- package/dist/helpers/index.js +4 -0
- package/dist/helpers/messages.d.ts +9 -0
- package/dist/helpers/messages.js +19 -0
- package/dist/helpers/pointers.d.ts +14 -9
- package/dist/helpers/pointers.js +23 -43
- package/dist/helpers/profile.d.ts +10 -2
- package/dist/helpers/profile.js +33 -4
- package/dist/helpers/reactions.d.ts +8 -0
- package/dist/helpers/reactions.js +56 -0
- package/dist/helpers/reports.d.ts +28 -0
- package/dist/helpers/reports.js +38 -0
- package/dist/helpers/share.d.ts +10 -1
- package/dist/helpers/share.js +22 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/observable/__tests__/exports.test.d.ts +1 -0
- package/dist/observable/__tests__/exports.test.js +21 -0
- package/dist/observable/__tests__/map-events-to-store.test.d.ts +1 -0
- package/dist/observable/__tests__/map-events-to-store.test.js +38 -0
- package/dist/observable/__tests__/watch-event-updates.test.d.ts +1 -0
- package/dist/observable/__tests__/watch-event-updates.test.js +55 -0
- package/dist/observable/index.d.ts +5 -1
- package/dist/observable/index.js +6 -1
- package/dist/observable/map-events-timeline.d.ts +7 -0
- package/dist/observable/map-events-timeline.js +9 -0
- package/dist/observable/map-events-to-store.d.ts +5 -0
- package/dist/observable/map-events-to-store.js +12 -0
- package/dist/observable/watch-event-updates.d.ts +7 -0
- package/dist/observable/watch-event-updates.js +14 -0
- package/dist/promise/__tests__/exports.test.d.ts +1 -0
- package/dist/promise/__tests__/exports.test.js +11 -0
- package/dist/queries/__tests__/comments.test.d.ts +1 -0
- package/dist/queries/__tests__/comments.test.js +39 -0
- package/dist/queries/__tests__/exports.test.d.ts +1 -0
- package/dist/queries/__tests__/exports.test.js +41 -0
- package/dist/queries/bookmarks.js +3 -3
- package/dist/queries/comments.js +4 -4
- package/dist/queries/contacts.js +3 -3
- package/dist/queries/mutes.js +3 -3
- package/dist/queries/relays.js +5 -5
- package/dist/query-store/__tests__/exports.test.d.ts +1 -0
- package/dist/query-store/__tests__/exports.test.js +12 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as exports from "../index.js";
|
|
3
|
+
describe("exports", () => {
|
|
4
|
+
it("should export the expected functions", () => {
|
|
5
|
+
expect(Object.keys(exports).sort()).toMatchInlineSnapshot(`
|
|
6
|
+
[
|
|
7
|
+
"Database",
|
|
8
|
+
"EventStore",
|
|
9
|
+
"EventStoreSymbol",
|
|
10
|
+
"Helpers",
|
|
11
|
+
"Queries",
|
|
12
|
+
"QueryStore",
|
|
13
|
+
"TimeoutError",
|
|
14
|
+
"defined",
|
|
15
|
+
"firstValueFrom",
|
|
16
|
+
"getObservableValue",
|
|
17
|
+
"lastValueFrom",
|
|
18
|
+
"listenLatestUpdates",
|
|
19
|
+
"logger",
|
|
20
|
+
"mapEventsToStore",
|
|
21
|
+
"mapEventsToTimeline",
|
|
22
|
+
"simpleTimeout",
|
|
23
|
+
"watchEventUpdates",
|
|
24
|
+
"withImmediateValueOrDefault",
|
|
25
|
+
]
|
|
26
|
+
`);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as core from "../index.js";
|
|
3
|
+
describe("core", () => {
|
|
4
|
+
it("should export the correct functions", () => {
|
|
5
|
+
expect(Object.keys(core).sort()).toMatchInlineSnapshot(`
|
|
6
|
+
[
|
|
7
|
+
"Database",
|
|
8
|
+
"EventStore",
|
|
9
|
+
"EventStoreSymbol",
|
|
10
|
+
"Helpers",
|
|
11
|
+
"Queries",
|
|
12
|
+
"QueryStore",
|
|
13
|
+
"logger",
|
|
14
|
+
]
|
|
15
|
+
`);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -98,6 +98,12 @@ describe("add", () => {
|
|
|
98
98
|
expect(eventStore.getEvent(newEvent.id)).toBeDefined();
|
|
99
99
|
expect(eventStore.getReplaceable(event.kind, event.pubkey, "test")).toBe(newEvent);
|
|
100
100
|
});
|
|
101
|
+
it("should return null when event is invalid and there isn't an existing event", () => {
|
|
102
|
+
const verifyEvent = vi.fn().mockReturnValue(false);
|
|
103
|
+
eventStore.verifyEvent = verifyEvent;
|
|
104
|
+
expect(eventStore.add(profile)).toBeNull();
|
|
105
|
+
expect(verifyEvent).toHaveBeenCalledWith(profile);
|
|
106
|
+
});
|
|
101
107
|
});
|
|
102
108
|
describe("inserts", () => {
|
|
103
109
|
it("should emit newer replaceable events", () => {
|
|
@@ -23,7 +23,7 @@ export declare class Database {
|
|
|
23
23
|
/** A stream of events removed from the database */
|
|
24
24
|
removed: Subject<import("nostr-tools").Event>;
|
|
25
25
|
/** A method thats called before a new event is inserted */
|
|
26
|
-
onBeforeInsert?: (event: NostrEvent) =>
|
|
26
|
+
onBeforeInsert?: (event: NostrEvent) => boolean;
|
|
27
27
|
get size(): number;
|
|
28
28
|
protected claims: WeakMap<import("nostr-tools").Event, any>;
|
|
29
29
|
/** Index helper methods */
|
|
@@ -41,9 +41,9 @@ export declare class Database {
|
|
|
41
41
|
/** Gets an array of replaceable events */
|
|
42
42
|
getReplaceable(kind: number, pubkey: string, d?: string): NostrEvent[] | undefined;
|
|
43
43
|
/** Inserts an event into the database and notifies all subscriptions */
|
|
44
|
-
addEvent(event: NostrEvent): NostrEvent;
|
|
44
|
+
addEvent(event: NostrEvent): NostrEvent | null;
|
|
45
45
|
/** Inserts and event into the database and notifies all subscriptions that the event has updated */
|
|
46
|
-
updateEvent(event: NostrEvent):
|
|
46
|
+
updateEvent(event: NostrEvent): boolean;
|
|
47
47
|
/** Removes an event from the database and notifies all subscriptions */
|
|
48
48
|
removeEvent(eventOrId: string | NostrEvent): boolean;
|
|
49
49
|
/** Sets the claim on the event and touches it */
|
|
@@ -86,7 +86,9 @@ export class Database {
|
|
|
86
86
|
const current = this.events.get(id);
|
|
87
87
|
if (current)
|
|
88
88
|
return current;
|
|
89
|
-
|
|
89
|
+
// Ignore events if before insert returns false
|
|
90
|
+
if (this.onBeforeInsert?.(event) === false)
|
|
91
|
+
return null;
|
|
90
92
|
this.events.set(id, event);
|
|
91
93
|
this.getKindIndex(event.kind).add(event);
|
|
92
94
|
this.getAuthorsIndex(event.pubkey).add(event);
|
|
@@ -115,8 +117,9 @@ export class Database {
|
|
|
115
117
|
/** Inserts and event into the database and notifies all subscriptions that the event has updated */
|
|
116
118
|
updateEvent(event) {
|
|
117
119
|
const inserted = this.addEvent(event);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
if (inserted)
|
|
121
|
+
this.updated.next(inserted);
|
|
122
|
+
return inserted !== null;
|
|
120
123
|
}
|
|
121
124
|
/** Removes an event from the database and notifies all subscriptions */
|
|
122
125
|
removeEvent(eventOrId) {
|
|
@@ -7,7 +7,10 @@ export declare class EventStore implements IEventStore {
|
|
|
7
7
|
database: Database;
|
|
8
8
|
/** Enable this to keep old versions of replaceable events */
|
|
9
9
|
keepOldVersions: boolean;
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* A method used to verify new events before added them
|
|
12
|
+
* @returns true if the event is valid, false if it should be ignored
|
|
13
|
+
*/
|
|
11
14
|
verifyEvent?: (event: NostrEvent) => boolean;
|
|
12
15
|
/** A stream of new events added to the store */
|
|
13
16
|
inserts: Observable<NostrEvent>;
|
|
@@ -23,20 +26,21 @@ export declare class EventStore implements IEventStore {
|
|
|
23
26
|
/** Copies important metadata from and identical event to another */
|
|
24
27
|
static mergeDuplicateEvent(source: NostrEvent, dest: NostrEvent): void;
|
|
25
28
|
/**
|
|
26
|
-
* Adds an event to the
|
|
27
|
-
* @
|
|
29
|
+
* Adds an event to the store and update subscriptions
|
|
30
|
+
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
28
31
|
*/
|
|
29
|
-
add(event: NostrEvent, fromRelay?: string): NostrEvent;
|
|
32
|
+
add(event: NostrEvent, fromRelay?: string): NostrEvent | null;
|
|
30
33
|
/** Removes an event from the database and updates subscriptions */
|
|
31
34
|
remove(event: string | NostrEvent): boolean;
|
|
32
35
|
/** Removes any event that is not being used by a subscription */
|
|
33
36
|
prune(max?: number): number;
|
|
34
37
|
/** Add an event to the store and notifies all subscribes it has updated */
|
|
35
|
-
update(event: NostrEvent):
|
|
38
|
+
update(event: NostrEvent): boolean;
|
|
36
39
|
/** Get all events matching a filter */
|
|
37
40
|
getAll(filters: Filter | Filter[]): Set<NostrEvent>;
|
|
38
|
-
/** Check if the store has an event */
|
|
41
|
+
/** Check if the store has an event by id */
|
|
39
42
|
hasEvent(id: string): boolean;
|
|
43
|
+
/** Get an event by id from the store */
|
|
40
44
|
getEvent(id: string): NostrEvent | undefined;
|
|
41
45
|
/** Check if the store has a replaceable event */
|
|
42
46
|
hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
|
|
@@ -18,7 +18,10 @@ export class EventStore {
|
|
|
18
18
|
database;
|
|
19
19
|
/** Enable this to keep old versions of replaceable events */
|
|
20
20
|
keepOldVersions = false;
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* A method used to verify new events before added them
|
|
23
|
+
* @returns true if the event is valid, false if it should be ignored
|
|
24
|
+
*/
|
|
22
25
|
verifyEvent;
|
|
23
26
|
/** A stream of new events added to the store */
|
|
24
27
|
inserts;
|
|
@@ -28,10 +31,13 @@ export class EventStore {
|
|
|
28
31
|
removes;
|
|
29
32
|
constructor() {
|
|
30
33
|
this.database = new Database();
|
|
34
|
+
// verify events before they are added to the database
|
|
31
35
|
this.database.onBeforeInsert = (event) => {
|
|
32
|
-
//
|
|
36
|
+
// Ignore events that are invalid
|
|
33
37
|
if (this.verifyEvent && this.verifyEvent(event) === false)
|
|
34
|
-
|
|
38
|
+
return false;
|
|
39
|
+
else
|
|
40
|
+
return true;
|
|
35
41
|
};
|
|
36
42
|
// when events are added to the database, add the symbol
|
|
37
43
|
this.database.inserted.subscribe((event) => {
|
|
@@ -100,8 +106,8 @@ export class EventStore {
|
|
|
100
106
|
Reflect.set(dest, FromCacheSymbol, fromCache);
|
|
101
107
|
}
|
|
102
108
|
/**
|
|
103
|
-
* Adds an event to the
|
|
104
|
-
* @
|
|
109
|
+
* Adds an event to the store and update subscriptions
|
|
110
|
+
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
105
111
|
*/
|
|
106
112
|
add(event, fromRelay) {
|
|
107
113
|
if (event.kind === kinds.EventDeletion)
|
|
@@ -130,6 +136,9 @@ export class EventStore {
|
|
|
130
136
|
}
|
|
131
137
|
// Insert event into database
|
|
132
138
|
const inserted = this.database.addEvent(event);
|
|
139
|
+
// If the event was ignored, return null
|
|
140
|
+
if (inserted === null)
|
|
141
|
+
return null;
|
|
133
142
|
// Copy cached data if its a duplicate event
|
|
134
143
|
if (event !== inserted)
|
|
135
144
|
EventStore.mergeDuplicateEvent(event, inserted);
|
|
@@ -167,10 +176,11 @@ export class EventStore {
|
|
|
167
176
|
getAll(filters) {
|
|
168
177
|
return this.database.getEventsForFilters(Array.isArray(filters) ? filters : [filters]);
|
|
169
178
|
}
|
|
170
|
-
/** Check if the store has an event */
|
|
179
|
+
/** Check if the store has an event by id */
|
|
171
180
|
hasEvent(id) {
|
|
172
181
|
return this.database.hasEvent(id);
|
|
173
182
|
}
|
|
183
|
+
/** Get an event by id from the store */
|
|
174
184
|
getEvent(id) {
|
|
175
185
|
return this.database.getEvent(id);
|
|
176
186
|
}
|
|
@@ -27,7 +27,7 @@ export interface IStreamEventStore {
|
|
|
27
27
|
timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
|
|
28
28
|
}
|
|
29
29
|
export interface IEventStore extends ISyncEventStore, IStreamEventStore {
|
|
30
|
-
add(event: NostrEvent, fromRelay?: string): NostrEvent;
|
|
30
|
+
add(event: NostrEvent, fromRelay?: string): NostrEvent | null;
|
|
31
31
|
remove(event: string | NostrEvent): boolean;
|
|
32
|
-
update(event: NostrEvent):
|
|
32
|
+
update(event: NostrEvent): void;
|
|
33
33
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { kinds } from "nostr-tools";
|
|
3
|
+
import { getHandlerSupportedKinds, getHandlerName, getHandlerPicture, getHandlerDescription, getHandlerLinkTemplate, createHandlerProfileLink, createHandlerEventLink, createHandlerAddressLink, createHandlerLink, } from "../app-handlers.js";
|
|
4
|
+
import { FakeUser } from "../../__tests__/fixtures.js";
|
|
5
|
+
import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode } from "nostr-tools/nip19";
|
|
6
|
+
// Create a fake user for testing
|
|
7
|
+
const user = new FakeUser();
|
|
8
|
+
// Mock handler event based on NIP-89 specification
|
|
9
|
+
const mockHandler = user.event({
|
|
10
|
+
kind: kinds.Handlerinformation,
|
|
11
|
+
tags: [
|
|
12
|
+
["d", "app-handler"],
|
|
13
|
+
["k", "0"], // supports profiles
|
|
14
|
+
["k", "1"], // supports notes
|
|
15
|
+
["k", "30023"], // supports long-form content
|
|
16
|
+
["web", "https://example.com/npub/<bech32>", "npub"],
|
|
17
|
+
["web", "https://example.com/profile?nprofile=<bech32>", "nprofile"],
|
|
18
|
+
["web", "https://example.com/note?id=<bech32>", "note"],
|
|
19
|
+
["web", "https://example.com/event?nevent=<bech32>", "nevent"],
|
|
20
|
+
["web", "https://example.com/article?naddr=<bech32>", "naddr"],
|
|
21
|
+
["ios", "testhandler://<bech32>"],
|
|
22
|
+
["android", "testhandler://<bech32>"],
|
|
23
|
+
],
|
|
24
|
+
content: JSON.stringify({
|
|
25
|
+
name: "Test Handler",
|
|
26
|
+
display_name: "Test Handler App",
|
|
27
|
+
picture: "https://example.com/logo.png",
|
|
28
|
+
about: "A test handler for NIP-89",
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
describe("getHandlerSupportedKinds", () => {
|
|
32
|
+
it("should return an array of supported kinds", () => {
|
|
33
|
+
expect(getHandlerSupportedKinds(mockHandler)).toEqual([0, 1, 30023]);
|
|
34
|
+
});
|
|
35
|
+
it("should return an empty array when no kinds are specified", () => {
|
|
36
|
+
const handlerWithoutKinds = user.event({
|
|
37
|
+
kind: kinds.Handlerinformation,
|
|
38
|
+
tags: mockHandler.tags.filter((tag) => tag[0] !== "k"),
|
|
39
|
+
content: mockHandler.content,
|
|
40
|
+
});
|
|
41
|
+
expect(getHandlerSupportedKinds(handlerWithoutKinds)).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("getHandlerName", () => {
|
|
45
|
+
it("should return the handler name", () => {
|
|
46
|
+
expect(getHandlerName(mockHandler)).toBe("Test Handler App");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("getHandlerPicture", () => {
|
|
50
|
+
it("should return the handler picture", () => {
|
|
51
|
+
const picture = getHandlerPicture(mockHandler);
|
|
52
|
+
expect(picture).toBe("https://example.com/logo.png");
|
|
53
|
+
});
|
|
54
|
+
it("should return the fallback when no picture is available", () => {
|
|
55
|
+
const handlerWithoutPicture = user.event({
|
|
56
|
+
kind: kinds.Handlerinformation,
|
|
57
|
+
tags: mockHandler.tags,
|
|
58
|
+
content: JSON.stringify({
|
|
59
|
+
name: "Test Handler",
|
|
60
|
+
display_name: "Test Handler App",
|
|
61
|
+
about: "A test handler for NIP-89",
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
const fallback = "https://fallback.com/default.png";
|
|
65
|
+
const picture = getHandlerPicture(handlerWithoutPicture, fallback);
|
|
66
|
+
expect(picture).toBe(fallback);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe("getHandlerDescription", () => {
|
|
70
|
+
it("should return the handler description", () => {
|
|
71
|
+
const description = getHandlerDescription(mockHandler);
|
|
72
|
+
expect(description).toBe("A test handler for NIP-89");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("getHandlerLinkTemplate", () => {
|
|
76
|
+
it("should return the web link template for a specific type", () => {
|
|
77
|
+
const template = getHandlerLinkTemplate(mockHandler, "web", "npub");
|
|
78
|
+
expect(template).toBe("https://example.com/npub/<bech32>");
|
|
79
|
+
});
|
|
80
|
+
it("should return the ios link template", () => {
|
|
81
|
+
const template = getHandlerLinkTemplate(mockHandler, "ios");
|
|
82
|
+
expect(template).toBe("testhandler://<bech32>");
|
|
83
|
+
});
|
|
84
|
+
it("should return undefined when no template exists", () => {
|
|
85
|
+
// @ts-expect-error - unknown-type is not a valid type
|
|
86
|
+
const template = getHandlerLinkTemplate(mockHandler, "web", "unknown-type");
|
|
87
|
+
expect(template).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe("createHandlerProfileLink", () => {
|
|
91
|
+
it("should create a profile link using nprofile format", () => {
|
|
92
|
+
const pointer = { pubkey: user.pubkey, relays: ["wss://relay.example.com"] };
|
|
93
|
+
expect(createHandlerProfileLink(mockHandler, pointer)).toEqual(`https://example.com/profile?nprofile=${nprofileEncode(pointer)}`);
|
|
94
|
+
});
|
|
95
|
+
it("should fallback to npub when nprofile template is not available", () => {
|
|
96
|
+
const handlerWithoutNprofile = user.event({
|
|
97
|
+
kind: kinds.Handlerinformation,
|
|
98
|
+
tags: mockHandler.tags.filter((tag) => !(tag[0] === "web" && tag[2] === "nprofile")),
|
|
99
|
+
content: mockHandler.content,
|
|
100
|
+
});
|
|
101
|
+
const pointer = { pubkey: user.pubkey, relays: ["wss://relay.example.com"] };
|
|
102
|
+
expect(createHandlerProfileLink(handlerWithoutNprofile, pointer)).toEqual(`https://example.com/npub/${npubEncode(pointer.pubkey)}`);
|
|
103
|
+
});
|
|
104
|
+
it("should use default when link type is not available", () => {
|
|
105
|
+
const handlerWithoutNprofile = user.event({
|
|
106
|
+
kind: kinds.Handlerinformation,
|
|
107
|
+
tags: [...mockHandler.tags.filter((tag) => !tag[2]), ["web", "https://example.com/<bech32>"]],
|
|
108
|
+
content: mockHandler.content,
|
|
109
|
+
});
|
|
110
|
+
const pointer = { pubkey: user.pubkey, relays: ["wss://relay.example.com"] };
|
|
111
|
+
expect(createHandlerProfileLink(handlerWithoutNprofile, pointer)).toEqual(`https://example.com/${nprofileEncode(pointer)}`);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe("createHandlerEventLink", () => {
|
|
115
|
+
it("should create an event link using nevent format", () => {
|
|
116
|
+
const pointer = { id: user.event({ kind: 1111, content: "hello" }).id, relays: ["wss://relay.example.com"] };
|
|
117
|
+
expect(createHandlerEventLink(mockHandler, pointer)).toEqual(`https://example.com/event?nevent=${neventEncode(pointer)}`);
|
|
118
|
+
});
|
|
119
|
+
it("should fallback to note when nevent template is not available", () => {
|
|
120
|
+
const handlerWithoutNevent = user.event({
|
|
121
|
+
kind: kinds.Handlerinformation,
|
|
122
|
+
tags: mockHandler.tags.filter((tag) => tag[2] !== "nevent"),
|
|
123
|
+
content: mockHandler.content,
|
|
124
|
+
});
|
|
125
|
+
const pointer = { id: user.note("hello").id, relays: ["wss://relay.example.com"] };
|
|
126
|
+
expect(createHandlerEventLink(handlerWithoutNevent, pointer)).toEqual(`https://example.com/note?id=${noteEncode(pointer.id)}`);
|
|
127
|
+
});
|
|
128
|
+
it("should use default when link type is not available", () => {
|
|
129
|
+
const handlerWithoutNevent = user.event({
|
|
130
|
+
kind: kinds.Handlerinformation,
|
|
131
|
+
tags: [...mockHandler.tags.filter((tag) => !tag[2]), ["web", "https://example.com/<bech32>"]],
|
|
132
|
+
content: mockHandler.content,
|
|
133
|
+
});
|
|
134
|
+
const pointer = { id: user.note("hello").id, relays: ["wss://relay.example.com"] };
|
|
135
|
+
expect(createHandlerEventLink(handlerWithoutNevent, pointer)).toEqual(`https://example.com/${neventEncode(pointer)}`);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe("createHandlerAddressLink", () => {
|
|
139
|
+
it("should create an address link using naddr format", () => {
|
|
140
|
+
const pointer = { identifier: "article1", pubkey: user.pubkey, kind: 30023, relays: ["wss://relay.example.com"] };
|
|
141
|
+
expect(createHandlerAddressLink(mockHandler, pointer)).toEqual(`https://example.com/article?naddr=${naddrEncode(pointer)}`);
|
|
142
|
+
});
|
|
143
|
+
it("should use default when link type is not available", () => {
|
|
144
|
+
const handlerWithoutNaddr = user.event({
|
|
145
|
+
kind: kinds.Handlerinformation,
|
|
146
|
+
tags: [...mockHandler.tags.filter((tag) => !tag[2]), ["web", "https://example.com/<bech32>"]],
|
|
147
|
+
content: mockHandler.content,
|
|
148
|
+
});
|
|
149
|
+
const pointer = { identifier: "article1", pubkey: user.pubkey, kind: 30023, relays: ["wss://relay.example.com"] };
|
|
150
|
+
expect(createHandlerAddressLink(handlerWithoutNaddr, pointer)).toEqual(`https://example.com/${naddrEncode(pointer)}`);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe("createHandlerLink", () => {
|
|
154
|
+
it("should create a profile link for profile pointers", () => {
|
|
155
|
+
const pointer = { pubkey: user.pubkey, relays: ["wss://relay.example.com"] };
|
|
156
|
+
expect(createHandlerLink(mockHandler, pointer)).toEqual(`https://example.com/profile?nprofile=${nprofileEncode(pointer)}`);
|
|
157
|
+
});
|
|
158
|
+
it("should create an event link for event pointers", () => {
|
|
159
|
+
const pointer = { id: user.event({ kind: 1111, content: "hello" }).id, relays: ["wss://relay.example.com"] };
|
|
160
|
+
expect(createHandlerLink(mockHandler, pointer)).toEqual(`https://example.com/event?nevent=${neventEncode(pointer)}`);
|
|
161
|
+
});
|
|
162
|
+
it("should create an address link for address pointers", () => {
|
|
163
|
+
const pointer = { identifier: "article1", pubkey: user.pubkey, kind: 30023, relays: ["wss://relay.example.com"] };
|
|
164
|
+
expect(createHandlerLink(mockHandler, pointer)).toEqual(`https://example.com/article?naddr=${naddrEncode(pointer)}`);
|
|
165
|
+
});
|
|
166
|
+
it("should fallback to web platform when specified platform has no template", () => {
|
|
167
|
+
const handlerWithoutAndroid = user.event({
|
|
168
|
+
kind: kinds.Handlerinformation,
|
|
169
|
+
tags: mockHandler.tags.filter((tag) => tag[0] !== "android"),
|
|
170
|
+
content: mockHandler.content,
|
|
171
|
+
});
|
|
172
|
+
const pointer = { pubkey: user.pubkey, relays: ["wss://relay.example.com"] };
|
|
173
|
+
expect(createHandlerLink(handlerWithoutAndroid, pointer, "android")).toContain("https://example.com/profile?nprofile=");
|
|
174
|
+
});
|
|
175
|
+
it("should not fallback to web platform when webFallback is false", () => {
|
|
176
|
+
const handlerWithoutAndroid = user.event({
|
|
177
|
+
kind: kinds.Handlerinformation,
|
|
178
|
+
tags: mockHandler.tags.filter((tag) => tag[0] !== "android"),
|
|
179
|
+
content: mockHandler.content,
|
|
180
|
+
});
|
|
181
|
+
const pointer = { pubkey: user.pubkey, relays: ["wss://relay.example.com"] };
|
|
182
|
+
expect(createHandlerLink(handlerWithoutAndroid, pointer, "android", false)).toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { getEmojiTag } from "../emoji.js";
|
|
2
|
+
import { getEmojiTag, getReactionEmoji } from "../emoji.js";
|
|
3
3
|
import { FakeUser } from "../../__tests__/fixtures.js";
|
|
4
4
|
const user = new FakeUser();
|
|
5
5
|
describe("getEmojiTag", () => {
|
|
@@ -13,3 +13,98 @@ describe("getEmojiTag", () => {
|
|
|
13
13
|
expect(getEmojiTag(user.note("hello :custom:", { tags: [["emoji", "custom", "https://cdn.example.com/reaction1.png"]] }), "CustoM")).toEqual(["emoji", "custom", "https://cdn.example.com/reaction1.png"]);
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
|
+
describe("getReactionEmoji", () => {
|
|
17
|
+
it("returns emoji object when content matches emoji tag", () => {
|
|
18
|
+
const event = user.event({
|
|
19
|
+
kind: 7,
|
|
20
|
+
tags: [["emoji", "heart", "https://cdn.example.com/heart.png"]],
|
|
21
|
+
content: ":heart:",
|
|
22
|
+
});
|
|
23
|
+
const result = getReactionEmoji(event);
|
|
24
|
+
expect(result).toEqual({
|
|
25
|
+
shortcode: "heart",
|
|
26
|
+
url: "https://cdn.example.com/heart.png",
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
it("should return undefined when content is invalid shortcode", () => {
|
|
30
|
+
const event = user.event({
|
|
31
|
+
kind: 7,
|
|
32
|
+
tags: [["emoji", "smile", "https://cdn.example.com/smile.png"]],
|
|
33
|
+
content: ":smile",
|
|
34
|
+
});
|
|
35
|
+
const result = getReactionEmoji(event);
|
|
36
|
+
expect(result).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
it("handles double colon issue", () => {
|
|
39
|
+
const event = user.event({
|
|
40
|
+
kind: 7,
|
|
41
|
+
tags: [["emoji", "smile", "https://cdn.example.com/smile.png"]],
|
|
42
|
+
content: "::smile::",
|
|
43
|
+
});
|
|
44
|
+
const result = getReactionEmoji(event);
|
|
45
|
+
expect(result).toEqual({
|
|
46
|
+
shortcode: "smile",
|
|
47
|
+
url: "https://cdn.example.com/smile.png",
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
it("trims whitespace from content", () => {
|
|
51
|
+
const event = user.event({
|
|
52
|
+
kind: 7,
|
|
53
|
+
tags: [["emoji", "thumbsup", "https://cdn.example.com/thumbsup.png"]],
|
|
54
|
+
content: " :thumbsup: ",
|
|
55
|
+
});
|
|
56
|
+
const result = getReactionEmoji(event);
|
|
57
|
+
expect(result).toEqual({
|
|
58
|
+
shortcode: "thumbsup",
|
|
59
|
+
url: "https://cdn.example.com/thumbsup.png",
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
it("returns undefined when emoji tag is missing", () => {
|
|
63
|
+
const event = user.event({
|
|
64
|
+
kind: 7,
|
|
65
|
+
tags: [["p", "pub1"]],
|
|
66
|
+
content: ":missing:",
|
|
67
|
+
});
|
|
68
|
+
const result = getReactionEmoji(event);
|
|
69
|
+
expect(result).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
it("returns undefined when content is empty", () => {
|
|
72
|
+
const event = user.event({
|
|
73
|
+
kind: 7,
|
|
74
|
+
tags: [["emoji", "star", "https://cdn.example.com/star.png"]],
|
|
75
|
+
content: "",
|
|
76
|
+
});
|
|
77
|
+
const result = getReactionEmoji(event);
|
|
78
|
+
expect(result).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
it("returns undefined when content is just colons", () => {
|
|
81
|
+
const event = user.event({
|
|
82
|
+
kind: 7,
|
|
83
|
+
tags: [["emoji", "fire", "https://cdn.example.com/fire.png"]],
|
|
84
|
+
content: "::",
|
|
85
|
+
});
|
|
86
|
+
const result = getReactionEmoji(event);
|
|
87
|
+
expect(result).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
it("returns undefined when emoji tag is invalid (missing url)", () => {
|
|
90
|
+
const event = user.event({
|
|
91
|
+
kind: 7,
|
|
92
|
+
tags: [["emoji", "invalid"]],
|
|
93
|
+
content: ":invalid:",
|
|
94
|
+
});
|
|
95
|
+
const result = getReactionEmoji(event);
|
|
96
|
+
expect(result).toBeUndefined();
|
|
97
|
+
});
|
|
98
|
+
it("handles capital letters", () => {
|
|
99
|
+
const event = user.event({
|
|
100
|
+
kind: 7,
|
|
101
|
+
tags: [["emoji", "heart", "https://cdn.example.com/heart.png"]],
|
|
102
|
+
content: ":HEART:",
|
|
103
|
+
});
|
|
104
|
+
const result = getReactionEmoji(event);
|
|
105
|
+
expect(result).toEqual({
|
|
106
|
+
shortcode: "heart",
|
|
107
|
+
url: "https://cdn.example.com/heart.png",
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|