Add tests

This commit is contained in:
Ticruz
2025-02-04 13:21:23 +01:00
committed by Jon Staab
parent 917727c86f
commit 8a2b62f693
57 changed files with 9231 additions and 25 deletions
+1
View File
@@ -1,2 +1,3 @@
build
normalize-url
__tests__
+150
View File
@@ -0,0 +1,150 @@
import {describe, it, expect} from "vitest"
import * as Content from "../src"
import {npubEncode, noteEncode} from "nostr-tools/nip19"
describe("Content Parsing", () => {
const npub = npubEncode("ee".repeat(32))
const nevent = noteEncode("ff".repeat(32))
describe("Basic Parsing", () => {
it("should parse plain text", () => {
const result = Content.parse({content: "Hello world"})
expect(result).toEqual([
{type: Content.ParsedType.Text, value: "Hello world", raw: "Hello world"},
])
})
it("should parse newlines", () => {
const result = Content.parse({content: "Hello\nworld"})
expect(result).toEqual([
{type: Content.ParsedType.Text, value: "Hello", raw: "Hello"},
{type: Content.ParsedType.Newline, value: "\n", raw: "\n"},
{type: Content.ParsedType.Text, value: "world", raw: "world"},
])
})
})
describe("Link Parsing", () => {
it("should parse basic URLs", () => {
const result = Content.parse({content: "Check https://example.com"})
expect(result[1]).toMatchObject({
type: Content.ParsedType.Link,
value: {
url: expect.any(URL),
isMedia: false,
},
})
expect(result[1].value.url.toString()).toBe("https://example.com/")
})
it("should parse URLs without protocol", () => {
const result = Content.parse({content: "Visit example.com"})
expect(result[1]).toMatchObject({
type: Content.ParsedType.Link,
value: {
url: expect.any(URL),
isMedia: false,
},
})
expect(result[1].value.url.toString()).toBe("https://example.com/")
})
it("should identify media links", () => {
const result = Content.parse({content: "https://example.com/image.jpg"})
expect(result[0]).toMatchObject({
type: Content.ParsedType.Link,
value: {
isMedia: true,
},
})
})
})
describe("Nostr Entity Parsing", () => {
it("should parse nostr profiles", () => {
const result = Content.parse({
content: `nostr:${npub}`,
})
expect(result[0]).toMatchObject({
type: Content.ParsedType.Profile,
})
})
it("should parse nostr events", () => {
const result = Content.parse({
content: `nostr:${nevent}`,
})
expect(result[0]).toMatchObject({
type: Content.ParsedType.Event,
})
})
})
describe("Special Content Parsing", () => {
it("should parse code blocks", () => {
const result = Content.parse({content: "```const x = 1```"})
expect(result[0]).toMatchObject({
type: Content.ParsedType.Code,
value: "const x = 1",
})
})
it("should parse inline code", () => {
const result = Content.parse({content: "Use `npm install`"})
expect(result[1]).toMatchObject({
type: Content.ParsedType.Code,
value: "npm install",
})
})
it("should parse topics", () => {
const result = Content.parse({content: "#nostr is cool"})
expect(result[0]).toMatchObject({
type: Content.ParsedType.Topic,
value: "nostr",
})
})
})
describe("Rendering", () => {
it("should render as text", () => {
const parsed = Content.parse({content: "Hello https://example.com"})
const rendered = Content.renderAsText(parsed).toString()
expect(rendered).toContain("Hello")
expect(rendered).toContain("https://example.com")
})
it("should render as HTML", () => {
const parsed = Content.parse({content: "Hello https://example.com"})
const rendered = Content.renderAsHtml(parsed).toString()
expect(rendered).toContain('<a href="https://example.com/"')
})
})
describe("Link Grid", () => {
it("should reduce consecutive image links into a grid", () => {
const content = Content.parse({
content: "https://example.com/1.jpg\nhttps://example.com/2.jpg https://example.com/2.jpg",
})
const reduced = Content.reduceLinks(content)
expect(reduced[0]).toMatchObject({
type: Content.ParsedType.LinkGrid,
value: {
links: expect.any(Array),
},
})
})
})
describe("Legacy Mention Parsing", () => {
it("should parse legacy mentions", () => {
const result = Content.parse({
content: "#[0]",
tags: [["p", "1234567890"]],
})
expect(result[0]).toMatchObject({
type: Content.ParsedType.Profile,
})
})
})
})
@@ -0,0 +1,69 @@
import {describe, it, expect, beforeEach} from "vitest"
import {htmlRenderOptions, Renderer, textRenderOptions} from "../src"
describe("Renderer", () => {
let renderer: Renderer
describe("Html renderer", () => {
beforeEach(() => {
renderer = new Renderer(htmlRenderOptions)
})
it("should render text", () => {
renderer.addText("Hello world")
expect(renderer.toString()).toBe("Hello world")
})
it("should render newlines", () => {
renderer.addNewlines(2)
expect(renderer.toString()).toBe("\n\n")
})
it("should render links", () => {
renderer.addLink("https://njump.me", "Example")
expect(renderer.toString()).toBe('<a href="https://njump.me/" target="_blank">Example</a>')
})
it("should render entities", () => {
renderer.addEntityLink("1234567890abcdef")
expect(renderer.toString()).toBe(
'<a href="https://njump.me/1234567890abcdef" target="_blank">1234567890abcdef…</a>',
)
})
it("should escape HTML in text content", () => {
renderer.addText('<script>alert("xss")</script>')
expect(renderer.toString()).not.toContain("<script>")
})
})
describe("Text renderer", () => {
beforeEach(() => {
renderer = new Renderer(textRenderOptions)
})
it("should render text", () => {
renderer.addText("Hello world")
expect(renderer.toString()).toBe("Hello world")
})
it("should render newlines", () => {
renderer.addNewlines(2)
expect(renderer.toString()).toBe("\n\n")
})
it("should render links", () => {
renderer.addLink("https://njump.me", "Example")
expect(renderer.toString()).toBe("https://njump.me")
})
it("should render entities", () => {
renderer.addEntityLink("1234567890abcdef")
expect(renderer.toString()).toBe("1234567890abcdef")
})
it("should escape HTML in text content", () => {
renderer.addText('<script>alert("xss")</script>')
expect(renderer.toString()).not.toContain("<script>")
})
})
})
+158
View File
@@ -0,0 +1,158 @@
import {describe, it, expect} from "vitest"
import {truncate, ParsedType, Parsed} from "../src"
describe("Content Truncation", () => {
it("should not truncate content shorter than minLength", () => {
const content: Parsed[] = [{type: ParsedType.Text, value: "Short text", raw: "Short text"}]
const result = truncate(content, {minLength: 20, maxLength: 30})
expect(result).toEqual(content)
})
it("should not truncate the first item even if it's longer than maxLength", () => {
const content: Parsed[] = [
{type: ParsedType.Text, value: "a".repeat(600), raw: "a".repeat(600)},
]
const result = truncate(content, {minLength: 400, maxLength: 600})
expect(result[0].type).toEqual(ParsedType.Text)
expect(result[1].type).toEqual(ParsedType.Ellipsis)
expect(result).toHaveLength(2)
})
it("should not truncate text content between minLength and maxLength", () => {
const content: Parsed[] = [
{type: ParsedType.Text, value: "a".repeat(600), raw: "a".repeat(600)},
{type: ParsedType.Newline, value: "\n", raw: "\n"},
]
const result = truncate(content, {minLength: 500, maxLength: 700})
expect(result).toHaveLength(2)
expect(result[1].type).toEqual(ParsedType.Newline)
})
it("should account for mediaLength in link calculations", () => {
const content: Parsed[] = [
{type: ParsedType.Text, value: "a".repeat(300), raw: "a".repeat(300)},
{
type: ParsedType.Link,
value: {
url: new URL("https://example.com/image.jpg"),
meta: {},
isMedia: true,
},
raw: "https://example.com/image.jpg",
},
]
const result = truncate(content, {
minLength: 400,
maxLength: 500,
mediaLength: 250,
})
expect(result[result.length - 1].type).toBe(ParsedType.Ellipsis) // ellipsis
expect(result).toHaveLength(2) // text + link = 300 + 250 = 550
})
it("should account for entityLength in nostr entity calculations", () => {
const content: Parsed[] = [
{type: ParsedType.Text, value: "a".repeat(300), raw: "a".repeat(300)},
{
type: ParsedType.Profile,
value: {
pubkey: "1234567890",
relays: [],
},
raw: "nostr:npub1...",
},
]
const result = truncate(content, {
minLength: 300,
maxLength: 400,
entityLength: 110,
})
// 300 + 110 = 410, which is over the maxLength
expect(result[result.length - 1].type).toBe(ParsedType.Ellipsis) // ellipsis
expect(result).toHaveLength(2) // text + profile
})
it("should handle mixed content types correctly", () => {
const content: Parsed[] = [
{type: ParsedType.Text, value: "a".repeat(200), raw: "a".repeat(200)},
{
type: ParsedType.Link,
value: {
url: new URL("https://example.com/image.jpg"),
meta: {},
isMedia: true,
},
raw: "https://example.com/image.jpg",
},
{type: ParsedType.Text, value: "b".repeat(200), raw: "b".repeat(200)},
{
type: ParsedType.Profile,
value: {
pubkey: "1234567890",
relays: [],
},
raw: "nostr:npub1...",
},
]
const result = truncate(content, {
minLength: 400,
maxLength: 500,
mediaLength: 200,
entityLength: 30,
})
expect(result[result.length - 1].type).toBe(ParsedType.Ellipsis)
})
it("should handle code blocks correctly", () => {
const content: Parsed[] = [
{type: ParsedType.Text, value: "a".repeat(200), raw: "a".repeat(200)},
{type: ParsedType.Code, value: "b".repeat(300), raw: "```" + "b".repeat(300) + "```"},
]
const result = truncate(content, {
minLength: 400,
maxLength: 500,
})
expect(result[result.length - 1].type).toBe(ParsedType.Ellipsis)
})
it("should handle invoice and cashu tokens", () => {
const content: Parsed[] = [
{type: ParsedType.Text, value: "a".repeat(200), raw: "a".repeat(200)},
{type: ParsedType.Invoice, value: "lnbc...", raw: "lnbc..."},
{type: ParsedType.Cashu, value: "cashu...", raw: "cashu..."},
]
const result = truncate(content, {
minLength: 300,
maxLength: 400,
mediaLength: 200,
})
expect(result[result.length - 1].type).toBe(ParsedType.Ellipsis)
})
it("should handle link grids", () => {
const content: Parsed[] = [
{type: ParsedType.Text, value: "a".repeat(200), raw: "a".repeat(200)},
{
type: ParsedType.LinkGrid,
value: {
links: [
{url: new URL("https://example.com/1.jpg"), meta: {}, isMedia: true},
{url: new URL("https://example.com/2.jpg"), meta: {}, isMedia: true},
],
},
raw: "",
},
]
const result = truncate(content, {
minLength: 300,
maxLength: 400,
mediaLength: 200,
})
expect(result[result.length - 1].type).toBe(ParsedType.Ellipsis)
})
})