at main 16 kB view raw
1import 'package:coves_flutter/models/comment.dart'; 2import 'package:flutter_test/flutter_test.dart'; 3 4void main() { 5 group('CommentsResponse', () { 6 test('should parse valid JSON with comments', () { 7 final json = { 8 'post': {'uri': 'at://test/post/123'}, 9 'cursor': 'next-cursor', 10 'comments': [ 11 { 12 'comment': { 13 'uri': 'at://did:plc:test/comment/1', 14 'cid': 'cid1', 15 'content': 'Test comment', 16 'createdAt': '2025-01-01T12:00:00Z', 17 'indexedAt': '2025-01-01T12:00:00Z', 18 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 19 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 20 'stats': {'upvotes': 10, 'downvotes': 2, 'score': 8}, 21 }, 22 'hasMore': false, 23 }, 24 ], 25 }; 26 27 final response = CommentsResponse.fromJson(json); 28 29 expect(response.comments.length, 1); 30 expect(response.cursor, 'next-cursor'); 31 expect(response.comments[0].comment.uri, 'at://did:plc:test/comment/1'); 32 expect(response.comments[0].comment.content, 'Test comment'); 33 }); 34 35 test('should handle null comments array', () { 36 final json = { 37 'post': {'uri': 'at://test/post/123'}, 38 'cursor': null, 39 'comments': null, 40 }; 41 42 final response = CommentsResponse.fromJson(json); 43 44 expect(response.comments, isEmpty); 45 expect(response.cursor, null); 46 }); 47 48 test('should handle empty comments array', () { 49 final json = { 50 'post': {'uri': 'at://test/post/123'}, 51 'cursor': null, 52 'comments': [], 53 }; 54 55 final response = CommentsResponse.fromJson(json); 56 57 expect(response.comments, isEmpty); 58 expect(response.cursor, null); 59 }); 60 61 test('should parse without cursor', () { 62 final json = { 63 'post': {'uri': 'at://test/post/123'}, 64 'comments': [ 65 { 66 'comment': { 67 'uri': 'at://did:plc:test/comment/1', 68 'cid': 'cid1', 69 'content': 'Test', 70 'createdAt': '2025-01-01T12:00:00Z', 71 'indexedAt': '2025-01-01T12:00:00Z', 72 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 73 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 74 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 75 }, 76 'hasMore': false, 77 }, 78 ], 79 }; 80 81 final response = CommentsResponse.fromJson(json); 82 83 expect(response.cursor, null); 84 expect(response.comments.length, 1); 85 }); 86 }); 87 88 group('ThreadViewComment', () { 89 test('should parse valid JSON', () { 90 final json = { 91 'comment': { 92 'uri': 'at://did:plc:test/comment/1', 93 'cid': 'cid1', 94 'content': 'Test comment', 95 'createdAt': '2025-01-01T12:00:00Z', 96 'indexedAt': '2025-01-01T12:00:00Z', 97 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 98 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 99 'stats': {'upvotes': 10, 'downvotes': 2, 'score': 8}, 100 }, 101 'hasMore': true, 102 }; 103 104 final thread = ThreadViewComment.fromJson(json); 105 106 expect(thread.comment.uri, 'at://did:plc:test/comment/1'); 107 expect(thread.hasMore, true); 108 expect(thread.replies, null); 109 }); 110 111 test('should parse with nested replies', () { 112 final json = { 113 'comment': { 114 'uri': 'at://did:plc:test/comment/1', 115 'cid': 'cid1', 116 'content': 'Parent comment', 117 'createdAt': '2025-01-01T12:00:00Z', 118 'indexedAt': '2025-01-01T12:00:00Z', 119 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 120 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 121 'stats': {'upvotes': 5, 'downvotes': 1, 'score': 4}, 122 }, 123 'replies': [ 124 { 125 'comment': { 126 'uri': 'at://did:plc:test/comment/2', 127 'cid': 'cid2', 128 'content': 'Reply comment', 129 'createdAt': '2025-01-01T13:00:00Z', 130 'indexedAt': '2025-01-01T13:00:00Z', 131 'author': {'did': 'did:plc:author2', 'handle': 'test.user2'}, 132 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 133 'parent': {'uri': 'at://did:plc:test/comment/1', 'cid': 'cid1'}, 134 'stats': {'upvotes': 3, 'downvotes': 0, 'score': 3}, 135 }, 136 'hasMore': false, 137 }, 138 ], 139 'hasMore': false, 140 }; 141 142 final thread = ThreadViewComment.fromJson(json); 143 144 expect(thread.comment.uri, 'at://did:plc:test/comment/1'); 145 expect(thread.replies, isNotNull); 146 expect(thread.replies!.length, 1); 147 expect(thread.replies![0].comment.uri, 'at://did:plc:test/comment/2'); 148 expect(thread.replies![0].comment.content, 'Reply comment'); 149 }); 150 151 test('should default hasMore to false when missing', () { 152 final json = { 153 'comment': { 154 'uri': 'at://did:plc:test/comment/1', 155 'cid': 'cid1', 156 'content': 'Test', 157 'createdAt': '2025-01-01T12:00:00Z', 158 'indexedAt': '2025-01-01T12:00:00Z', 159 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 160 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 161 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 162 }, 163 }; 164 165 final thread = ThreadViewComment.fromJson(json); 166 167 expect(thread.hasMore, false); 168 }); 169 }); 170 171 group('CommentView', () { 172 test('should parse complete JSON', () { 173 final json = { 174 'uri': 'at://did:plc:test/comment/1', 175 'cid': 'cid1', 176 'content': 'Test comment content', 177 'contentFacets': [ 178 { 179 'type': 'mention', 180 'index': {'start': 0, 'end': 10}, 181 'features': [ 182 { 183 'type': 'app.bsky.richtext.facet#mention', 184 'did': 'did:plc:mentioned', 185 }, 186 ], 187 }, 188 ], 189 'createdAt': '2025-01-01T12:00:00Z', 190 'indexedAt': '2025-01-01T12:05:00Z', 191 'author': { 192 'did': 'did:plc:author', 193 'handle': 'test.user', 194 'displayName': 'Test User', 195 }, 196 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 197 'parent': { 198 'uri': 'at://did:plc:test/comment/parent', 199 'cid': 'parent-cid', 200 }, 201 'stats': {'upvotes': 10, 'downvotes': 2, 'score': 8}, 202 'viewer': {'vote': 'upvote'}, 203 'embed': {'type': 'social.coves.embed.external', 'data': {}}, 204 }; 205 206 final comment = CommentView.fromJson(json); 207 208 expect(comment.uri, 'at://did:plc:test/comment/1'); 209 expect(comment.cid, 'cid1'); 210 expect(comment.content, 'Test comment content'); 211 expect(comment.contentFacets, isNotNull); 212 expect(comment.contentFacets!.length, 1); 213 expect(comment.createdAt, DateTime.parse('2025-01-01T12:00:00Z')); 214 expect(comment.indexedAt, DateTime.parse('2025-01-01T12:05:00Z')); 215 expect(comment.author.did, 'did:plc:author'); 216 expect(comment.post.uri, 'at://did:plc:test/post/123'); 217 expect(comment.parent, isNotNull); 218 expect(comment.parent!.uri, 'at://did:plc:test/comment/parent'); 219 expect(comment.stats.score, 8); 220 expect(comment.viewer, isNotNull); 221 expect(comment.viewer!.vote, 'upvote'); 222 expect(comment.embed, isNotNull); 223 }); 224 225 test('should parse minimal JSON with required fields only', () { 226 final json = { 227 'uri': 'at://did:plc:test/comment/1', 228 'cid': 'cid1', 229 'content': 'Test', 230 'createdAt': '2025-01-01T12:00:00Z', 231 'indexedAt': '2025-01-01T12:00:00Z', 232 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 233 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 234 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 235 }; 236 237 final comment = CommentView.fromJson(json); 238 239 expect(comment.uri, 'at://did:plc:test/comment/1'); 240 expect(comment.content, 'Test'); 241 expect(comment.contentFacets, null); 242 expect(comment.parent, null); 243 expect(comment.viewer, null); 244 expect(comment.embed, null); 245 }); 246 247 test('should handle null optional fields', () { 248 final json = { 249 'uri': 'at://did:plc:test/comment/1', 250 'cid': 'cid1', 251 'content': 'Test', 252 'contentFacets': null, 253 'createdAt': '2025-01-01T12:00:00Z', 254 'indexedAt': '2025-01-01T12:00:00Z', 255 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 256 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 257 'parent': null, 258 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 259 'viewer': null, 260 'embed': null, 261 }; 262 263 final comment = CommentView.fromJson(json); 264 265 expect(comment.contentFacets, null); 266 expect(comment.parent, null); 267 expect(comment.viewer, null); 268 expect(comment.embed, null); 269 }); 270 271 test('should parse dates correctly', () { 272 final json = { 273 'uri': 'at://did:plc:test/comment/1', 274 'cid': 'cid1', 275 'content': 'Test', 276 'createdAt': '2025-01-15T14:30:45.123Z', 277 'indexedAt': '2025-01-15T14:30:50.456Z', 278 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 279 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 280 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 281 }; 282 283 final comment = CommentView.fromJson(json); 284 285 expect(comment.createdAt.year, 2025); 286 expect(comment.createdAt.month, 1); 287 expect(comment.createdAt.day, 15); 288 expect(comment.createdAt.hour, 14); 289 expect(comment.createdAt.minute, 30); 290 expect(comment.indexedAt, isA<DateTime>()); 291 }); 292 }); 293 294 group('CommentRef', () { 295 test('should parse valid JSON', () { 296 final json = {'uri': 'at://did:plc:test/comment/1', 'cid': 'cid1'}; 297 298 final ref = CommentRef.fromJson(json); 299 300 expect(ref.uri, 'at://did:plc:test/comment/1'); 301 expect(ref.cid, 'cid1'); 302 }); 303 }); 304 305 group('CommentStats', () { 306 test('should parse valid JSON with all fields', () { 307 final json = {'upvotes': 15, 'downvotes': 3, 'score': 12}; 308 309 final stats = CommentStats.fromJson(json); 310 311 expect(stats.upvotes, 15); 312 expect(stats.downvotes, 3); 313 expect(stats.score, 12); 314 }); 315 316 test('should default to zero for missing fields', () { 317 final json = <String, dynamic>{}; 318 319 final stats = CommentStats.fromJson(json); 320 321 expect(stats.upvotes, 0); 322 expect(stats.downvotes, 0); 323 expect(stats.score, 0); 324 }); 325 326 test('should handle null values with defaults', () { 327 final json = {'upvotes': null, 'downvotes': null, 'score': null}; 328 329 final stats = CommentStats.fromJson(json); 330 331 expect(stats.upvotes, 0); 332 expect(stats.downvotes, 0); 333 expect(stats.score, 0); 334 }); 335 336 test('should parse mixed null and valid values', () { 337 final json = {'upvotes': 10, 'downvotes': null, 'score': 8}; 338 339 final stats = CommentStats.fromJson(json); 340 341 expect(stats.upvotes, 10); 342 expect(stats.downvotes, 0); 343 expect(stats.score, 8); 344 }); 345 }); 346 347 group('CommentViewerState', () { 348 test('should parse with vote', () { 349 final json = {'vote': 'upvote'}; 350 351 final viewer = CommentViewerState.fromJson(json); 352 353 expect(viewer.vote, 'upvote'); 354 }); 355 356 test('should parse with downvote', () { 357 final json = {'vote': 'downvote'}; 358 359 final viewer = CommentViewerState.fromJson(json); 360 361 expect(viewer.vote, 'downvote'); 362 }); 363 364 test('should parse with null vote', () { 365 final json = {'vote': null}; 366 367 final viewer = CommentViewerState.fromJson(json); 368 369 expect(viewer.vote, null); 370 }); 371 372 test('should handle missing vote field', () { 373 final json = <String, dynamic>{}; 374 375 final viewer = CommentViewerState.fromJson(json); 376 377 expect(viewer.vote, null); 378 }); 379 }); 380 381 group('Edge cases', () { 382 test('should handle deeply nested comment threads', () { 383 final json = { 384 'comment': { 385 'uri': 'at://did:plc:test/comment/1', 386 'cid': 'cid1', 387 'content': 'Level 1', 388 'createdAt': '2025-01-01T12:00:00Z', 389 'indexedAt': '2025-01-01T12:00:00Z', 390 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 391 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 392 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 393 }, 394 'replies': [ 395 { 396 'comment': { 397 'uri': 'at://did:plc:test/comment/2', 398 'cid': 'cid2', 399 'content': 'Level 2', 400 'createdAt': '2025-01-01T12:00:00Z', 401 'indexedAt': '2025-01-01T12:00:00Z', 402 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 403 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 404 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 405 }, 406 'replies': [ 407 { 408 'comment': { 409 'uri': 'at://did:plc:test/comment/3', 410 'cid': 'cid3', 411 'content': 'Level 3', 412 'createdAt': '2025-01-01T12:00:00Z', 413 'indexedAt': '2025-01-01T12:00:00Z', 414 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 415 'post': { 416 'uri': 'at://did:plc:test/post/123', 417 'cid': 'post-cid', 418 }, 419 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 420 }, 421 'hasMore': false, 422 }, 423 ], 424 'hasMore': false, 425 }, 426 ], 427 'hasMore': false, 428 }; 429 430 final thread = ThreadViewComment.fromJson(json); 431 432 expect(thread.comment.content, 'Level 1'); 433 expect(thread.replies![0].comment.content, 'Level 2'); 434 expect(thread.replies![0].replies![0].comment.content, 'Level 3'); 435 }); 436 437 test('should handle empty content string', () { 438 final json = { 439 'uri': 'at://did:plc:test/comment/1', 440 'cid': 'cid1', 441 'content': '', 442 'createdAt': '2025-01-01T12:00:00Z', 443 'indexedAt': '2025-01-01T12:00:00Z', 444 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 445 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 446 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 447 }; 448 449 final comment = CommentView.fromJson(json); 450 451 expect(comment.content, ''); 452 }); 453 454 test('should handle very long content', () { 455 final longContent = 'a' * 10000; 456 final json = { 457 'uri': 'at://did:plc:test/comment/1', 458 'cid': 'cid1', 459 'content': longContent, 460 'createdAt': '2025-01-01T12:00:00Z', 461 'indexedAt': '2025-01-01T12:00:00Z', 462 'author': {'did': 'did:plc:author', 'handle': 'test.user'}, 463 'post': {'uri': 'at://did:plc:test/post/123', 'cid': 'post-cid'}, 464 'stats': {'upvotes': 0, 'downvotes': 0, 'score': 0}, 465 }; 466 467 final comment = CommentView.fromJson(json); 468 469 expect(comment.content.length, 10000); 470 }); 471 472 test('should handle negative vote counts', () { 473 final json = {'upvotes': 5, 'downvotes': 20, 'score': -15}; 474 475 final stats = CommentStats.fromJson(json); 476 477 expect(stats.upvotes, 5); 478 expect(stats.downvotes, 20); 479 expect(stats.score, -15); 480 }); 481 }); 482}