Barazo default frontend barazo.forum
at main 221 lines 6.9 kB view raw
1/** 2 * Tests for reply tree builder utility. 3 */ 4 5import { describe, it, expect } from 'vitest' 6import type { Reply } from '@/lib/api/types' 7import { buildReplyTree, flattenReplyTree, countDescendants } from './build-reply-tree' 8 9const TOPIC_URI = 'at://did:plc:user-001/forum.barazo.topic.post/abc123' 10const TOPIC_CID = 'bafyreib1' 11const COMMUNITY_DID = 'did:plc:community-001' 12 13function makeReply( 14 overrides: Partial<Reply> & { uri: string; parentUri: string; depth: number } 15): Reply { 16 return { 17 rkey: overrides.uri.split('/').pop()!, 18 authorDid: 'did:plc:user-001', 19 content: 'Test reply', 20 rootUri: TOPIC_URI, 21 rootCid: TOPIC_CID, 22 parentCid: 'bafyreir0', 23 communityDid: COMMUNITY_DID, 24 cid: `cid-${overrides.uri}`, 25 reactionCount: 0, 26 isAuthorDeleted: false, 27 isModDeleted: false, 28 createdAt: '2026-02-14T12:00:00.000Z', 29 indexedAt: '2026-02-14T12:00:00.000Z', 30 ...overrides, 31 } 32} 33 34describe('buildReplyTree', () => { 35 it('returns empty roots for empty array', () => { 36 const result = buildReplyTree([], TOPIC_URI) 37 expect(result).toEqual([]) 38 }) 39 40 it('places direct reply to topic as root', () => { 41 const reply = makeReply({ 42 uri: 'at://user/reply/aaa', 43 parentUri: TOPIC_URI, 44 depth: 1, 45 }) 46 47 const result = buildReplyTree([reply], TOPIC_URI) 48 expect(result).toHaveLength(1) 49 expect(result[0]!.reply.uri).toBe(reply.uri) 50 expect(result[0]!.children).toEqual([]) 51 }) 52 53 it('nests child reply under its parent', () => { 54 const parent = makeReply({ 55 uri: 'at://user/reply/aaa', 56 parentUri: TOPIC_URI, 57 depth: 1, 58 }) 59 const child = makeReply({ 60 uri: 'at://user/reply/bbb', 61 parentUri: 'at://user/reply/aaa', 62 depth: 2, 63 }) 64 65 const result = buildReplyTree([parent, child], TOPIC_URI) 66 expect(result).toHaveLength(1) 67 expect(result[0]!.children).toHaveLength(1) 68 expect(result[0]!.children[0]!.reply.uri).toBe(child.uri) 69 }) 70 71 it('treats orphaned reply (parent not in array) as root', () => { 72 const orphan = makeReply({ 73 uri: 'at://user/reply/bbb', 74 parentUri: 'at://user/reply/missing', 75 depth: 2, 76 }) 77 78 const result = buildReplyTree([orphan], TOPIC_URI) 79 expect(result).toHaveLength(1) 80 expect(result[0]!.reply.uri).toBe(orphan.uri) 81 }) 82 83 it('maintains chronological order for multiple root-level replies', () => { 84 const first = makeReply({ 85 uri: 'at://user/reply/aaa', 86 parentUri: TOPIC_URI, 87 depth: 1, 88 createdAt: '2026-02-14T10:00:00.000Z', 89 }) 90 const second = makeReply({ 91 uri: 'at://user/reply/bbb', 92 parentUri: TOPIC_URI, 93 depth: 1, 94 createdAt: '2026-02-14T11:00:00.000Z', 95 }) 96 const third = makeReply({ 97 uri: 'at://user/reply/ccc', 98 parentUri: TOPIC_URI, 99 depth: 1, 100 createdAt: '2026-02-14T12:00:00.000Z', 101 }) 102 103 const result = buildReplyTree([first, second, third], TOPIC_URI) 104 expect(result).toHaveLength(3) 105 expect(result[0]!.reply.uri).toBe(first.uri) 106 expect(result[1]!.reply.uri).toBe(second.uri) 107 expect(result[2]!.reply.uri).toBe(third.uri) 108 }) 109 110 it('handles deep nesting (5+ levels)', () => { 111 const replies: Reply[] = [] 112 for (let i = 1; i <= 6; i++) { 113 replies.push( 114 makeReply({ 115 uri: `at://user/reply/${String(i).padStart(3, '0')}`, 116 parentUri: i === 1 ? TOPIC_URI : `at://user/reply/${String(i - 1).padStart(3, '0')}`, 117 depth: i, 118 }) 119 ) 120 } 121 122 const result = buildReplyTree(replies, TOPIC_URI) 123 expect(result).toHaveLength(1) 124 125 let node = result[0]! 126 for (let i = 0; i < 5; i++) { 127 expect(node.children).toHaveLength(1) 128 node = node.children[0]! 129 } 130 expect(node.children).toHaveLength(0) 131 }) 132 133 it('handles mixed order input (children before parents)', () => { 134 const child = makeReply({ 135 uri: 'at://user/reply/bbb', 136 parentUri: 'at://user/reply/aaa', 137 depth: 2, 138 }) 139 const parent = makeReply({ 140 uri: 'at://user/reply/aaa', 141 parentUri: TOPIC_URI, 142 depth: 1, 143 }) 144 145 // child comes before parent in input 146 const result = buildReplyTree([child, parent], TOPIC_URI) 147 expect(result).toHaveLength(1) 148 expect(result[0]!.reply.uri).toBe(parent.uri) 149 expect(result[0]!.children).toHaveLength(1) 150 expect(result[0]!.children[0]!.reply.uri).toBe(child.uri) 151 }) 152}) 153 154describe('countDescendants', () => { 155 it('returns 0 for a leaf node', () => { 156 const leaf = makeReply({ uri: 'at://user/reply/aaa', parentUri: TOPIC_URI, depth: 1 }) 157 expect(countDescendants({ reply: leaf, children: [] })).toBe(0) 158 }) 159 160 it('counts all descendants recursively', () => { 161 const replies = [ 162 makeReply({ uri: 'at://user/reply/a', parentUri: TOPIC_URI, depth: 1 }), 163 makeReply({ uri: 'at://user/reply/b', parentUri: 'at://user/reply/a', depth: 2 }), 164 makeReply({ uri: 'at://user/reply/c', parentUri: 'at://user/reply/b', depth: 3 }), 165 makeReply({ uri: 'at://user/reply/d', parentUri: 'at://user/reply/c', depth: 4 }), 166 ] 167 const tree = buildReplyTree(replies, TOPIC_URI) 168 // Root node has 3 descendants: b, c, d 169 expect(countDescendants(tree[0]!)).toBe(3) 170 // Node b has 2 descendants: c, d 171 expect(countDescendants(tree[0]!.children[0]!)).toBe(2) 172 // Node c has 1 descendant: d 173 expect(countDescendants(tree[0]!.children[0]!.children[0]!)).toBe(1) 174 }) 175 176 it('counts branching descendants', () => { 177 const replies = [ 178 makeReply({ uri: 'at://user/reply/a', parentUri: TOPIC_URI, depth: 1 }), 179 makeReply({ uri: 'at://user/reply/b', parentUri: 'at://user/reply/a', depth: 2 }), 180 makeReply({ uri: 'at://user/reply/c', parentUri: 'at://user/reply/a', depth: 2 }), 181 makeReply({ uri: 'at://user/reply/d', parentUri: 'at://user/reply/b', depth: 3 }), 182 ] 183 const tree = buildReplyTree(replies, TOPIC_URI) 184 // Root has 3 descendants: b, c, d 185 expect(countDescendants(tree[0]!)).toBe(3) 186 }) 187}) 188 189describe('flattenReplyTree', () => { 190 it('returns empty array for empty roots', () => { 191 expect(flattenReplyTree([])).toEqual([]) 192 }) 193 194 it('returns depth-first order', () => { 195 const root = makeReply({ 196 uri: 'at://user/reply/aaa', 197 parentUri: TOPIC_URI, 198 depth: 1, 199 }) 200 const child = makeReply({ 201 uri: 'at://user/reply/bbb', 202 parentUri: 'at://user/reply/aaa', 203 depth: 2, 204 }) 205 const grandchild = makeReply({ 206 uri: 'at://user/reply/ccc', 207 parentUri: 'at://user/reply/bbb', 208 depth: 3, 209 }) 210 const root2 = makeReply({ 211 uri: 'at://user/reply/ddd', 212 parentUri: TOPIC_URI, 213 depth: 1, 214 }) 215 216 const tree = buildReplyTree([root, child, grandchild, root2], TOPIC_URI) 217 const flat = flattenReplyTree(tree) 218 219 expect(flat.map((r) => r.uri)).toEqual([root.uri, child.uri, grandchild.uri, root2.uri]) 220 }) 221})