clone of repo on github
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

320 lines
9.4 KiB

import { beforeEach, describe, expect, it, vi } from "vitest";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import type NDK from "@nostr-dev-kit/ndk";
import { fetchHighlightsForPublication } from "../../src/lib/utils/fetch_publication_highlights.ts";
// Mock NDKEvent class
class MockNDKEvent {
kind: number;
pubkey: string;
content: string;
tags: string[][];
created_at: number;
id: string;
sig: string;
constructor(event: {
kind: number;
pubkey: string;
content: string;
tags: string[][];
created_at?: number;
id?: string;
sig?: string;
}) {
this.kind = event.kind;
this.pubkey = event.pubkey;
this.content = event.content;
this.tags = event.tags;
this.created_at = event.created_at || Date.now() / 1000;
this.id = event.id || "mock-id";
this.sig = event.sig || "mock-sig";
}
getMatchingTags(tagName: string): string[][] {
return this.tags.filter((tag) => tag[0] === tagName);
}
tagValue(tagName: string): string | undefined {
const tag = this.tags.find((tag) => tag[0] === tagName);
return tag ? tag[1] : undefined;
}
}
describe("fetchHighlightsForPublication", () => {
let mockNDK: NDK;
let publicationEvent: NDKEvent;
let mockHighlights: MockNDKEvent[];
beforeEach(() => {
// Create the sample 30040 publication event from the user's example
publicationEvent = new MockNDKEvent({
kind: 30040,
pubkey:
"fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1",
content: "",
tags: [
["d", "document-test"],
["title", "Document Test"],
["author", "unknown"],
["version", "1"],
["m", "application/json"],
["M", "meta-data/index/replaceable"],
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading",
],
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:another-first-level-heading",
],
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:a-third-first-level-heading",
],
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:asciimath-test-document",
],
["t", "a-tags"],
["t", "testfile"],
["t", "asciimath"],
["t", "latexmath"],
["image", "https://i.nostr.build/5kWwbDR04joIASVx.png"],
],
created_at: 1744910311,
id: "4585ed74a0be37655aa887340d239f0bbb9df5476165d912f098c55a71196fef",
sig:
"e6a832dcfc919c913acee62cb598211544bc8e03a3f61c016eb3bf6c8cb4fb333eff8fecc601517604c7a8029dfa73591f3218465071a532f4abfe8c0bf3662d",
}) as unknown as NDKEvent;
// Create mock highlight events for different sections
mockHighlights = [
new MockNDKEvent({
kind: 9802,
pubkey: "user-pubkey-1",
content: "This is an interesting point",
tags: [
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading",
],
["context", "surrounding text here"],
[
"p",
"fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1",
"",
"author",
],
],
id: "highlight-1",
}),
new MockNDKEvent({
kind: 9802,
pubkey: "user-pubkey-2",
content: "Another highlight on same section",
tags: [
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading",
],
["context", "more surrounding text"],
[
"p",
"fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1",
"",
"author",
],
],
id: "highlight-2",
}),
new MockNDKEvent({
kind: 9802,
pubkey: "user-pubkey-3",
content: "Highlight on different section",
tags: [
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:another-first-level-heading",
],
["context", "different section text"],
[
"p",
"fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1",
"",
"author",
],
],
id: "highlight-3",
}),
];
// Mock NDK instance
mockNDK = {
fetchEvents: vi.fn(async (filter) => {
// Return highlights that match the filter
const aTagFilter = filter["#a"];
if (aTagFilter) {
return new Set(
mockHighlights.filter((highlight) =>
aTagFilter.includes(highlight.tagValue("a") || "")
),
);
}
return new Set();
}),
} as unknown as NDK;
});
it("should extract section references from 30040 publication event", async () => {
const result = await fetchHighlightsForPublication(
publicationEvent,
mockNDK,
);
// Should have results for the sections that have highlights
expect(result.size).toBeGreaterThan(0);
expect(
result.has(
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading",
),
).toBe(true);
});
it("should fetch highlights for each section reference", async () => {
const result = await fetchHighlightsForPublication(
publicationEvent,
mockNDK,
);
// First section should have 2 highlights
const firstSectionHighlights = result.get(
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading",
);
expect(firstSectionHighlights?.length).toBe(2);
// Second section should have 1 highlight
const secondSectionHighlights = result.get(
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:another-first-level-heading",
);
expect(secondSectionHighlights?.length).toBe(1);
});
it("should group highlights by section address", async () => {
const result = await fetchHighlightsForPublication(
publicationEvent,
mockNDK,
);
const firstSectionHighlights = result.get(
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading",
);
// Verify the highlights are correctly grouped
expect(firstSectionHighlights?.[0].content).toBe(
"This is an interesting point",
);
expect(firstSectionHighlights?.[1].content).toBe(
"Another highlight on same section",
);
});
it("should not include sections without highlights", async () => {
const result = await fetchHighlightsForPublication(
publicationEvent,
mockNDK,
);
// Sections without highlights should not be in the result
expect(
result.has(
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:a-third-first-level-heading",
),
).toBe(false);
expect(
result.has(
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:asciimath-test-document",
),
).toBe(false);
});
it("should handle publication with no section references", async () => {
const emptyPublication = new MockNDKEvent({
kind: 30040,
pubkey: "test-pubkey",
content: "",
tags: [
["d", "empty-doc"],
["title", "Empty Document"],
],
}) as unknown as NDKEvent;
const result = await fetchHighlightsForPublication(
emptyPublication,
mockNDK,
);
expect(result.size).toBe(0);
});
it("should only process 30041 kind references, ignoring other a-tags", async () => {
const mixedPublication = new MockNDKEvent({
kind: 30040,
pubkey: "test-pubkey",
content: "",
tags: [
["d", "mixed-doc"],
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading",
],
["a", "30023:some-pubkey:blog-post"], // Different kind, should be ignored
["a", "1:some-pubkey"], // Different kind, should be ignored
],
}) as unknown as NDKEvent;
const result = await fetchHighlightsForPublication(
mixedPublication,
mockNDK,
);
// Should call fetchEvents with only the 30041 reference
expect(mockNDK.fetchEvents).toHaveBeenCalledWith(
expect.objectContaining({
kinds: [9802],
"#a": [
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading",
],
}),
);
});
it("should handle d-tags with colons correctly", async () => {
const colonPublication = new MockNDKEvent({
kind: 30040,
pubkey: "test-pubkey",
content: "",
tags: [
["d", "colon-doc"],
[
"a",
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:section:with:colons",
],
],
}) as unknown as NDKEvent;
const result = await fetchHighlightsForPublication(
colonPublication,
mockNDK,
);
// Should correctly parse the section address with colons
expect(mockNDK.fetchEvents).toHaveBeenCalledWith(
expect.objectContaining({
"#a": [
"30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:section:with:colons",
],
}),
);
});
});