Barazo default frontend barazo.forum
at main 128 lines 3.6 kB view raw
1/** 2 * Tests for formatting utilities. 3 */ 4 5import { describe, it, expect } from 'vitest' 6import { 7 formatRelativeTime, 8 formatCompactNumber, 9 slugify, 10 getTopicUrl, 11 getReplyUrl, 12 isEdited, 13} from './format' 14 15describe('formatRelativeTime', () => { 16 it('returns "just now" for recent timestamps', () => { 17 const now = new Date() 18 expect(formatRelativeTime(now.toISOString())).toBe('just now') 19 }) 20 21 it('returns minutes ago', () => { 22 const date = new Date(Date.now() - 5 * 60 * 1000) 23 expect(formatRelativeTime(date.toISOString())).toBe('5m ago') 24 }) 25 26 it('returns hours ago', () => { 27 const date = new Date(Date.now() - 3 * 60 * 60 * 1000) 28 expect(formatRelativeTime(date.toISOString())).toBe('3h ago') 29 }) 30}) 31 32describe('formatCompactNumber', () => { 33 it('returns number as-is below 1000', () => { 34 expect(formatCompactNumber(42)).toBe('42') 35 }) 36 37 it('formats thousands with k suffix', () => { 38 expect(formatCompactNumber(1200)).toBe('1.2k') 39 }) 40 41 it('formats millions with M suffix', () => { 42 expect(formatCompactNumber(3400000)).toBe('3.4M') 43 }) 44}) 45 46describe('slugify', () => { 47 it('converts title to lowercase slug', () => { 48 expect(slugify('Hello World')).toBe('hello-world') 49 }) 50 51 it('removes special characters', () => { 52 expect(slugify('Building with the AT Protocol!')).toBe('building-with-the-at-protocol') 53 }) 54 55 it('collapses multiple hyphens', () => { 56 expect(slugify('Hello --- World')).toBe('hello-world') 57 }) 58 59 it('trims leading/trailing hyphens', () => { 60 expect(slugify('---Hello World---')).toBe('hello-world') 61 }) 62 63 it('handles empty string', () => { 64 expect(slugify('')).toBe('untitled') 65 }) 66 67 it('truncates long slugs', () => { 68 const longTitle = 'A'.repeat(200) 69 const slug = slugify(longTitle) 70 expect(slug.length).toBeLessThanOrEqual(80) 71 }) 72}) 73 74describe('getTopicUrl', () => { 75 it('generates correct URL from topic', () => { 76 const topic = { 77 authorHandle: 'jay.bsky.team', 78 rkey: '3kf1abc', 79 } 80 expect(getTopicUrl(topic)).toBe('/jay.bsky.team/3kf1abc') 81 }) 82 83 it('handles different handles', () => { 84 const topic = { 85 authorHandle: 'alex.example.com', 86 rkey: '3kf3ghi', 87 } 88 expect(getTopicUrl(topic)).toBe('/alex.example.com/3kf3ghi') 89 }) 90}) 91 92describe('getReplyUrl', () => { 93 it('generates correct reply permalink URL', () => { 94 expect( 95 getReplyUrl({ 96 topicAuthorHandle: 'jay.bsky.team', 97 topicRkey: '3kf1abc', 98 replyAuthorHandle: 'alex.example.com', 99 replyRkey: '3kf2def', 100 }) 101 ).toBe('/jay.bsky.team/3kf1abc/alex.example.com/3kf2def') 102 }) 103}) 104 105describe('isEdited', () => { 106 it('returns false when timestamps are equal', () => { 107 const ts = '2026-01-01T12:00:00.000Z' 108 expect(isEdited(ts, ts)).toBe(false) 109 }) 110 111 it('returns false when difference is under 30 seconds', () => { 112 const createdAt = '2026-01-01T12:00:00.000Z' 113 const indexedAt = new Date(new Date(createdAt).getTime() + 15_000).toISOString() 114 expect(isEdited(createdAt, indexedAt)).toBe(false) 115 }) 116 117 it('returns true when difference exceeds 30 seconds', () => { 118 const createdAt = '2026-01-01T12:00:00.000Z' 119 const indexedAt = new Date(new Date(createdAt).getTime() + 60_000).toISOString() 120 expect(isEdited(createdAt, indexedAt)).toBe(true) 121 }) 122 123 it('returns false for invalid date strings', () => { 124 expect(isEdited('not-a-date', '2026-01-01T12:00:00.000Z')).toBe(false) 125 expect(isEdited('2026-01-01T12:00:00.000Z', 'not-a-date')).toBe(false) 126 expect(isEdited('invalid', 'also-invalid')).toBe(false) 127 }) 128})