Add tests
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
build
|
||||
normalize-url
|
||||
__tests__
|
||||
@@ -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>")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user