From bcd99994a9a56aebb65a91bd88e10f51ce6ea691 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Sat, 26 Sep 2026 19:45:48 +0700 Subject: [PATCH] test: cover zap helpers and newsletter data - useZapTotals: zapTargetKey (regular id vs addressable coord) and zapReceiptSats (amount tag -> bolt11 -> description JSON -> 0, malformed safe) - newsletterIssues: schema, newest-first ordering, publish hashtags --- src/hooks/useZapTotals.test.ts | 85 ++++++++++++++++++++++++++++++++ src/lib/newsletterIssues.test.ts | 36 ++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/hooks/useZapTotals.test.ts create mode 100644 src/lib/newsletterIssues.test.ts diff --git a/src/hooks/useZapTotals.test.ts b/src/hooks/useZapTotals.test.ts new file mode 100644 index 0000000..d9bdef0 --- /dev/null +++ b/src/hooks/useZapTotals.test.ts @@ -0,0 +1,85 @@ +import { describe, it, expect } from 'vitest'; +import type { NostrEvent } from '@nostrify/nostrify'; +import { zapTargetKey, zapReceiptSats } from './useZapTotals'; + +/** Minimal event factory — only the fields these helpers read. */ +function ev(partial: Partial & Pick): NostrEvent { + return { + content: '', + created_at: 0, + sig: '', + tags: [], + ...partial, + } as NostrEvent; +} + +describe('zapTargetKey', () => { + it('uses the event id for regular (non-addressable) events', () => { + const e = ev({ kind: 1, pubkey: 'aa', id: 'abc123' }); + expect(zapTargetKey(e)).toBe('abc123'); + }); + + it('uses the addressable coordinate for kind 30000-39999', () => { + const e = ev({ + kind: 30818, + pubkey: 'deadbeef', + id: 'ignored', + tags: [['d', 'tilcayo']], + }); + expect(zapTargetKey(e)).toBe('30818:deadbeef:tilcayo'); + }); + + it('handles an addressable event with a missing d tag', () => { + const e = ev({ kind: 30818, pubkey: 'deadbeef', id: 'ignored' }); + expect(zapTargetKey(e)).toBe('30818:deadbeef:'); + }); +}); + +describe('zapReceiptSats', () => { + it('reads the amount tag (millisats -> sats)', () => { + const receipt = ev({ + kind: 9735, + pubkey: 'lnurl-server', + id: 'r1', + tags: [['amount', '21000']], + }); + expect(zapReceiptSats(receipt)).toBe(21); + }); + + it('falls back to the bolt11 invoice amount', () => { + const receipt = ev({ + kind: 9735, + pubkey: 'lnurl-server', + id: 'r2', + tags: [['bolt11', 'lnbc210n1pvjluezpp5abcdef']], + }); + expect(zapReceiptSats(receipt)).toBe(21); + }); + + it('falls back to the zap request in the description tag', () => { + const receipt = ev({ + kind: 9735, + pubkey: 'lnurl-server', + id: 'r3', + tags: [ + ['description', JSON.stringify({ tags: [['amount', '1000000']] })], + ], + }); + expect(zapReceiptSats(receipt)).toBe(1000); + }); + + it('returns 0 when no amount can be determined', () => { + const receipt = ev({ kind: 9735, pubkey: 'lnurl-server', id: 'r4' }); + expect(zapReceiptSats(receipt)).toBe(0); + }); + + it('returns 0 for a malformed description instead of throwing', () => { + const receipt = ev({ + kind: 9735, + pubkey: 'lnurl-server', + id: 'r5', + tags: [['description', '{not json']], + }); + expect(zapReceiptSats(receipt)).toBe(0); + }); +}); diff --git a/src/lib/newsletterIssues.test.ts b/src/lib/newsletterIssues.test.ts new file mode 100644 index 0000000..7bcd7e0 --- /dev/null +++ b/src/lib/newsletterIssues.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect } from 'vitest'; +import { NEWSLETTER_ISSUES } from './newsletterIssues'; + +describe('NEWSLETTER_ISSUES data', () => { + it('has at least one issue', () => { + expect(NEWSLETTER_ISSUES.length).toBeGreaterThan(0); + }); + + it('every issue satisfies the schema', () => { + for (const issue of NEWSLETTER_ISSUES) { + expect(typeof issue.number).toBe('number'); + expect(issue.date).toMatch(/^\d{4}-\d{2}-\d{2}$/); + expect(issue.vibe.trim().length).toBeGreaterThan(0); + expect(issue.communityNote.trim().length).toBeGreaterThan(0); + expect(issue.nostrText.trim().length).toBeGreaterThan(0); + expect(Array.isArray(issue.updates)).toBe(true); + expect(issue.updates.length).toBeGreaterThan(0); + for (const u of issue.updates) { + expect(u.title.trim().length).toBeGreaterThan(0); + expect(u.body.trim().length).toBeGreaterThan(0); + expect(u.tag.trim().length).toBeGreaterThan(0); + } + } + }); + + it('is ordered newest-first (descending issue numbers)', () => { + for (let i = 1; i < NEWSLETTER_ISSUES.length; i++) { + expect(NEWSLETTER_ISSUES[i - 1].number).toBeGreaterThan(NEWSLETTER_ISSUES[i].number); + } + }); + + it('includes the d-tagged hashtags used to publish issues', () => { + const withNewsTag = NEWSLETTER_ISSUES.filter((i) => i.nostrText.includes('#skatespotsnews')); + expect(withNewsTag.length).toBeGreaterThan(0); + }); +});