An ATproto social media client -- with an independent Appview.
at main 1019 lines 31 kB view raw
1import {RichText} from '@atproto/api' 2 3import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' 4import { 5 createStarterPackGooglePlayUri, 6 createStarterPackLinkFromAndroidReferrer, 7 parseStarterPackUri, 8} from '#/lib/strings/starter-pack' 9import {tenorUrlToBskyGifUrl} from '#/state/queries/tenor' 10import {cleanError} from '../../src/lib/strings/errors' 11import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' 12import {enforceLen} from '../../src/lib/strings/helpers' 13import {detectLinkables} from '../../src/lib/strings/rich-text-detection' 14import {shortenLinks} from '../../src/lib/strings/rich-text-manip' 15import { 16 makeRecordUri, 17 toNiceDomain, 18 toShareUrl, 19 toShortUrl, 20} from '../../src/lib/strings/url-helpers' 21 22describe('detectLinkables', () => { 23 const inputs = [ 24 'no linkable', 25 '@start middle end', 26 'start @middle end', 27 'start middle @end', 28 '@start @middle @end', 29 '@full123.test-of-chars', 30 'not@right', 31 '@bad!@#$chars', 32 '@newline1\n@newline2', 33 'parenthetical (@handle)', 34 'start https://middle.com end', 35 'start https://middle.com/foo/bar end', 36 'start https://middle.com/foo/bar?baz=bux end', 37 'start https://middle.com/foo/bar?baz=bux#hash end', 38 'https://start.com/foo/bar?baz=bux#hash middle end', 39 'start middle https://end.com/foo/bar?baz=bux#hash', 40 'https://newline1.com\nhttps://newline2.com', 41 'start middle.com end', 42 'start middle.com/foo/bar end', 43 'start middle.com/foo/bar?baz=bux end', 44 'start middle.com/foo/bar?baz=bux#hash end', 45 'start.com/foo/bar?baz=bux#hash middle end', 46 'start middle end.com/foo/bar?baz=bux#hash', 47 'newline1.com\nnewline2.com', 48 'not.. a..url ..here', 49 'e.g.', 50 'e.g. real.com fake.notreal', 51 'something-cool.jpg', 52 'website.com.jpg', 53 'e.g./foo', 54 'website.com.jpg/foo', 55 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 56 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/ ', 57 'https://foo.com https://bar.com/whatever https://baz.com', 58 'punctuation https://foo.com, https://bar.com/whatever; https://baz.com.', 59 'parenthetical (https://foo.com)', 60 'except for https://foo.com/thing_(cool)', 61 ] 62 const outputs = [ 63 ['no linkable'], 64 [{link: '@start'}, ' middle end'], 65 ['start ', {link: '@middle'}, ' end'], 66 ['start middle ', {link: '@end'}], 67 [{link: '@start'}, ' ', {link: '@middle'}, ' ', {link: '@end'}], 68 [{link: '@full123.test-of-chars'}], 69 ['not@right'], 70 [{link: '@bad'}, '!@#$chars'], 71 [{link: '@newline1'}, '\n', {link: '@newline2'}], 72 ['parenthetical (', {link: '@handle'}, ')'], 73 ['start ', {link: 'https://middle.com'}, ' end'], 74 ['start ', {link: 'https://middle.com/foo/bar'}, ' end'], 75 ['start ', {link: 'https://middle.com/foo/bar?baz=bux'}, ' end'], 76 ['start ', {link: 'https://middle.com/foo/bar?baz=bux#hash'}, ' end'], 77 [{link: 'https://start.com/foo/bar?baz=bux#hash'}, ' middle end'], 78 ['start middle ', {link: 'https://end.com/foo/bar?baz=bux#hash'}], 79 [{link: 'https://newline1.com'}, '\n', {link: 'https://newline2.com'}], 80 ['start ', {link: 'middle.com'}, ' end'], 81 ['start ', {link: 'middle.com/foo/bar'}, ' end'], 82 ['start ', {link: 'middle.com/foo/bar?baz=bux'}, ' end'], 83 ['start ', {link: 'middle.com/foo/bar?baz=bux#hash'}, ' end'], 84 [{link: 'start.com/foo/bar?baz=bux#hash'}, ' middle end'], 85 ['start middle ', {link: 'end.com/foo/bar?baz=bux#hash'}], 86 [{link: 'newline1.com'}, '\n', {link: 'newline2.com'}], 87 ['not.. a..url ..here'], 88 ['e.g.'], 89 ['e.g. ', {link: 'real.com'}, ' fake.notreal'], 90 ['something-cool.jpg'], 91 ['website.com.jpg'], 92 ['e.g./foo'], 93 ['website.com.jpg/foo'], 94 [ 95 'Classic article ', 96 { 97 link: 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 98 }, 99 ], 100 [ 101 'Classic article ', 102 { 103 link: 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 104 }, 105 ' ', 106 ], 107 [ 108 {link: 'https://foo.com'}, 109 ' ', 110 {link: 'https://bar.com/whatever'}, 111 ' ', 112 {link: 'https://baz.com'}, 113 ], 114 [ 115 'punctuation ', 116 {link: 'https://foo.com'}, 117 ', ', 118 {link: 'https://bar.com/whatever'}, 119 '; ', 120 {link: 'https://baz.com'}, 121 '.', 122 ], 123 ['parenthetical (', {link: 'https://foo.com'}, ')'], 124 ['except for ', {link: 'https://foo.com/thing_(cool)'}], 125 ] 126 it('correctly handles a set of text inputs', () => { 127 for (let i = 0; i < inputs.length; i++) { 128 const input = inputs[i] 129 const output = detectLinkables(input) 130 expect(output).toEqual(outputs[i]) 131 } 132 }) 133}) 134 135describe('makeRecordUri', () => { 136 const inputs: [string, string, string][] = [ 137 ['alice.test', 'app.bsky.feed.post', '3jk7x4irgv52r'], 138 ] 139 const outputs = ['at://alice.test/app.bsky.feed.post/3jk7x4irgv52r'] 140 141 it('correctly builds a record URI', () => { 142 for (let i = 0; i < inputs.length; i++) { 143 const input = inputs[i] 144 const result = makeRecordUri(...input) 145 expect(result).toEqual(outputs[i]) 146 } 147 }) 148}) 149 150describe('makeValidHandle', () => { 151 const inputs = [ 152 'test-handle-123', 153 'test!"#$%&/()=?_', 154 'this-handle-should-be-too-big', 155 ] 156 const outputs = ['test-handle-123', 'test', 'this-handle-should-b'] 157 158 it('correctly parses and corrects handles', () => { 159 for (let i = 0; i < inputs.length; i++) { 160 const result = makeValidHandle(inputs[i]) 161 expect(result).toEqual(outputs[i]) 162 } 163 }) 164}) 165 166describe('createFullHandle', () => { 167 const inputs: [string, string][] = [ 168 ['test-handle-123', 'test'], 169 ['.test.handle', 'test.test.'], 170 ['test.handle.', '.test.test'], 171 ] 172 const outputs = [ 173 'test-handle-123.test', 174 '.test.handle.test.test.', 175 'test.handle.test.test', 176 ] 177 178 it('correctly parses and corrects handles', () => { 179 for (let i = 0; i < inputs.length; i++) { 180 const input = inputs[i] 181 const result = createFullHandle(...input) 182 expect(result).toEqual(outputs[i]) 183 } 184 }) 185}) 186 187describe('enforceLen', () => { 188 const inputs: [string, number][] = [ 189 ['Hello World!', 5], 190 ['Hello World!', 20], 191 ['', 5], 192 ] 193 const outputs = ['Hello', 'Hello World!', ''] 194 195 it('correctly enforces defined length on a given string', () => { 196 for (let i = 0; i < inputs.length; i++) { 197 const input = inputs[i] 198 const result = enforceLen(...input) 199 expect(result).toEqual(outputs[i]) 200 } 201 }) 202}) 203 204describe('cleanError', () => { 205 const inputs = [ 206 'TypeError: Network request failed', 207 'Error: Aborted', 208 'Error: TypeError "x" is not a function', 209 'Error: SyntaxError unexpected token "export"', 210 'Some other error', 211 ] 212 const outputs = [ 213 'Unable to connect. Please check your internet connection and try again.', 214 'Unable to connect. Please check your internet connection and try again.', 215 'TypeError "x" is not a function', 216 'SyntaxError unexpected token "export"', 217 'Some other error', 218 ] 219 220 it('removes extra content from error message', () => { 221 for (let i = 0; i < inputs.length; i++) { 222 const result = cleanError(inputs[i]) 223 expect(result).toEqual(outputs[i]) 224 } 225 }) 226}) 227 228describe('toNiceDomain', () => { 229 const inputs = [ 230 'https://example.com/index.html', 231 'https://bsky.app', 232 'https://bsky.social', 233 '#123123123', 234 ] 235 const outputs = ['example.com', 'bsky.app', 'Bluesky Social', '#123123123'] 236 237 it("displays the url's host in a easily readable manner", () => { 238 for (let i = 0; i < inputs.length; i++) { 239 const result = toNiceDomain(inputs[i]) 240 expect(result).toEqual(outputs[i]) 241 } 242 }) 243}) 244 245describe('toShortUrl', () => { 246 const inputs = [ 247 'https://bsky.app', 248 'https://bsky.app/3jk7x4irgv52r', 249 'https://bsky.app/3jk7x4irgv52r2313y182h9', 250 'https://very-long-domain-name.com/foo', 251 'https://very-long-domain-name.com/foo?bar=baz#andsomemore', 252 ] 253 const outputs = [ 254 'bsky.app', 255 'bsky.app/3jk7x4irgv52r', 256 'bsky.app/3jk7x4irgv52...', 257 'very-long-domain-name.com/foo', 258 'very-long-domain-name.com/foo?bar=baz#...', 259 ] 260 261 it('shortens the url', () => { 262 for (let i = 0; i < inputs.length; i++) { 263 const result = toShortUrl(inputs[i]) 264 expect(result).toEqual(outputs[i]) 265 } 266 }) 267}) 268 269describe('toShareUrl', () => { 270 const inputs = [ 271 'https://social.shatteredsky.net', 272 '/3jk7x4irgv52r', 273 'item/test/123', 274 ] 275 const outputs = [ 276 'https://social.shatteredsky.net', 277 'https://social.shatteredsky.net/3jk7x4irgv52r', 278 'https://social.shatteredsky.net/item/test/123', 279 ] 280 281 it('appends https, when not present', () => { 282 for (let i = 0; i < inputs.length; i++) { 283 const result = toShareUrl(inputs[i]) 284 expect(result).toEqual(outputs[i]) 285 } 286 }) 287}) 288 289describe('shortenLinks', () => { 290 const inputs = [ 291 'start https://middle.com/foo/bar?baz=bux#hash end', 292 'https://start.com/foo/bar?baz=bux#hash middle end', 293 'start middle https://end.com/foo/bar?baz=bux#hash', 294 'https://newline1.com/very/long/url/here\nhttps://newline2.com/very/long/url/here', 295 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 296 ] 297 const outputs = [ 298 [ 299 'start middle.com/foo/bar?baz=... end', 300 ['https://middle.com/foo/bar?baz=bux#hash'], 301 ], 302 [ 303 'start.com/foo/bar?baz=... middle end', 304 ['https://start.com/foo/bar?baz=bux#hash'], 305 ], 306 [ 307 'start middle end.com/foo/bar?baz=...', 308 ['https://end.com/foo/bar?baz=bux#hash'], 309 ], 310 [ 311 'newline1.com/very/long/ur...\nnewline2.com/very/long/ur...', 312 [ 313 'https://newline1.com/very/long/url/here', 314 'https://newline2.com/very/long/url/here', 315 ], 316 ], 317 [ 318 'Classic article socket3.wordpress.com/2018/02/03/d...', 319 [ 320 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/', 321 ], 322 ], 323 ] 324 325 it('correctly shortens rich text while preserving facet URIs', () => { 326 for (let i = 0; i < inputs.length; i++) { 327 const input = inputs[i] 328 const inputRT = new RichText({text: input}) 329 inputRT.detectFacetsWithoutResolution() 330 const outputRT = shortenLinks(inputRT) 331 expect(outputRT.text).toEqual(outputs[i][0]) 332 expect(outputRT.facets?.length).toEqual(outputs[i][1].length) 333 for (let j = 0; j < outputs[i][1].length; j++) { 334 expect(outputRT.facets![j].features[0].uri).toEqual(outputs[i][1][j]) 335 } 336 } 337 }) 338}) 339 340describe('parseEmbedPlayerFromUrl', () => { 341 const inputs = [ 342 'https://youtu.be/videoId', 343 'https://youtu.be/videoId?t=1s', 344 'https://www.youtube.com/watch?v=videoId', 345 'https://www.youtube.com/watch?v=videoId&feature=share', 346 'https://www.youtube.com/watch?v=videoId&t=1s', 347 'https://youtube.com/watch?v=videoId', 348 'https://youtube.com/watch?v=videoId&feature=share', 349 'https://youtube.com/shorts/videoId', 350 'https://youtube.com/live/videoId', 351 'https://m.youtube.com/watch?v=videoId', 352 'https://music.youtube.com/watch?v=videoId', 353 354 'https://youtube.com/shorts/', 355 'https://youtube.com/', 356 'https://youtube.com/random', 357 'https://youtube.com/live/', 358 359 'https://twitch.tv/channelName', 360 'https://www.twitch.tv/channelName', 361 'https://m.twitch.tv/channelName', 362 363 'https://twitch.tv/channelName/clip/clipId', 364 'https://twitch.tv/videos/videoId', 365 366 'https://open.spotify.com/playlist/playlistId', 367 'https://open.spotify.com/playlist/playlistId?param=value', 368 'https://open.spotify.com/locale/playlist/playlistId', 369 370 'https://open.spotify.com/track/songId', 371 'https://open.spotify.com/track/songId?param=value', 372 'https://open.spotify.com/locale/track/songId', 373 374 'https://open.spotify.com/album/albumId', 375 'https://open.spotify.com/album/albumId?param=value', 376 'https://open.spotify.com/locale/album/albumId', 377 378 'https://soundcloud.com/user/track', 379 'https://soundcloud.com/user/sets/set', 380 'https://soundcloud.com/user/', 381 382 'https://music.apple.com/us/playlist/playlistName/playlistId', 383 'https://music.apple.com/us/album/albumName/albumId', 384 'https://music.apple.com/us/album/albumName/albumId?i=songId', 385 386 'https://vimeo.com/videoId', 387 'https://vimeo.com/videoId?autoplay=0', 388 389 'https://giphy.com/gifs/some-random-gif-name-gifId', 390 'https://giphy.com/gif/some-random-gif-name-gifId', 391 'https://giphy.com/gifs/', 392 393 'https://giphy.com/gifs/39248209509382934029?hh=100&ww=100', 394 395 'https://media.giphy.com/media/gifId/giphy.webp', 396 'https://media0.giphy.com/media/gifId/giphy.webp', 397 'https://media1.giphy.com/media/gifId/giphy.gif', 398 'https://media2.giphy.com/media/gifId/giphy.webp', 399 'https://media3.giphy.com/media/gifId/giphy.mp4', 400 'https://media4.giphy.com/media/gifId/giphy.webp', 401 'https://media5.giphy.com/media/gifId/giphy.mp4', 402 'https://media0.giphy.com/media/gifId/giphy.mp3', 403 'https://media1.google.com/media/gifId/giphy.webp', 404 405 'https://media.giphy.com/media/trackingId/gifId/giphy.webp', 406 407 'https://i.giphy.com/media/gifId/giphy.webp', 408 'https://i.giphy.com/media/gifId/giphy.webp', 409 'https://i.giphy.com/gifId.gif', 410 'https://i.giphy.com/gifId.gif', 411 412 'https://tenor.com/view/gifId', 413 'https://tenor.com/notView/gifId', 414 'https://tenor.com/view', 415 'https://tenor.com/view/gifId.gif', 416 'https://tenor.com/intl/view/gifId.gif', 417 418 'https://media.tenor.com/someID_AAAAC/someName.gif?hh=100&ww=100', 419 'https://media.tenor.com/someID_AAAAC/someName.gif', 420 'https://media.tenor.com/someID/someName.gif', 421 'https://media.tenor.com/someID', 422 'https://media.tenor.com', 423 424 'https://www.flickr.com/photos/username/albums/72177720308493661', 425 'https://flickr.com/photos/username/albums/72177720308493661', 426 'https://flickr.com/photos/username/albums/72177720308493661/', 427 'https://flickr.com/photos/username/albums/72177720308493661//', 428 'https://flic.kr/s/aHBqjAES3i', 429 430 'https://flickr.com/foetoes/username/albums/3903', 431 'https://flickr.com/albums/3903', 432 'https://flic.kr/s/OolI', 433 'https://flic.kr/t/aHBqjAES3i', 434 435 'https://www.flickr.com/groups/898944@N23/pool', 436 'https://flickr.com/groups/898944@N23/pool', 437 'https://flickr.com/groups/898944@N23/pool/', 438 'https://flickr.com/groups/898944@N23/pool//', 439 'https://flic.kr/go/8WJtR', 440 441 'https://www.flickr.com/groups/898944@N23/', 442 'https://www.flickr.com/groups', 443 ] 444 445 const outputs = [ 446 { 447 type: 'youtube_video', 448 source: 'youtube', 449 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 450 }, 451 { 452 type: 'youtube_video', 453 source: 'youtube', 454 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=1', 455 }, 456 { 457 type: 'youtube_video', 458 source: 'youtube', 459 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 460 }, 461 { 462 type: 'youtube_video', 463 source: 'youtube', 464 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 465 }, 466 { 467 type: 'youtube_video', 468 source: 'youtube', 469 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=1', 470 }, 471 { 472 type: 'youtube_video', 473 source: 'youtube', 474 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 475 }, 476 { 477 type: 'youtube_video', 478 source: 'youtube', 479 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 480 }, 481 { 482 type: 'youtube_short', 483 source: 'youtubeShorts', 484 hideDetails: true, 485 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 486 }, 487 { 488 type: 'youtube_video', 489 source: 'youtube', 490 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 491 }, 492 { 493 type: 'youtube_video', 494 source: 'youtube', 495 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 496 }, 497 { 498 type: 'youtube_video', 499 source: 'youtube', 500 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', 501 }, 502 503 undefined, 504 undefined, 505 undefined, 506 undefined, 507 508 { 509 type: 'twitch_video', 510 source: 'twitch', 511 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`, 512 }, 513 { 514 type: 'twitch_video', 515 source: 'twitch', 516 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`, 517 }, 518 { 519 type: 'twitch_video', 520 source: 'twitch', 521 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`, 522 }, 523 { 524 type: 'twitch_video', 525 source: 'twitch', 526 playerUri: `https://clips.twitch.tv/embed?volume=0.5&autoplay=true&clip=clipId&parent=localhost`, 527 }, 528 { 529 type: 'twitch_video', 530 source: 'twitch', 531 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&video=videoId&parent=localhost`, 532 }, 533 534 { 535 type: 'spotify_playlist', 536 source: 'spotify', 537 playerUri: `https://open.spotify.com/embed/playlist/playlistId`, 538 }, 539 { 540 type: 'spotify_playlist', 541 source: 'spotify', 542 playerUri: `https://open.spotify.com/embed/playlist/playlistId`, 543 }, 544 { 545 type: 'spotify_playlist', 546 source: 'spotify', 547 playerUri: `https://open.spotify.com/embed/playlist/playlistId`, 548 }, 549 550 { 551 type: 'spotify_song', 552 source: 'spotify', 553 playerUri: `https://open.spotify.com/embed/track/songId`, 554 }, 555 { 556 type: 'spotify_song', 557 source: 'spotify', 558 playerUri: `https://open.spotify.com/embed/track/songId`, 559 }, 560 { 561 type: 'spotify_song', 562 source: 'spotify', 563 playerUri: `https://open.spotify.com/embed/track/songId`, 564 }, 565 566 { 567 type: 'spotify_album', 568 source: 'spotify', 569 playerUri: `https://open.spotify.com/embed/album/albumId`, 570 }, 571 { 572 type: 'spotify_album', 573 source: 'spotify', 574 playerUri: `https://open.spotify.com/embed/album/albumId`, 575 }, 576 { 577 type: 'spotify_album', 578 source: 'spotify', 579 playerUri: `https://open.spotify.com/embed/album/albumId`, 580 }, 581 582 { 583 type: 'soundcloud_track', 584 source: 'soundcloud', 585 playerUri: `https://w.soundcloud.com/player/?url=https://soundcloud.com/user/track&auto_play=true&visual=false&hide_related=true`, 586 }, 587 { 588 type: 'soundcloud_set', 589 source: 'soundcloud', 590 playerUri: `https://w.soundcloud.com/player/?url=https://soundcloud.com/user/sets/set&auto_play=true&visual=false&hide_related=true`, 591 }, 592 undefined, 593 594 { 595 type: 'apple_music_playlist', 596 source: 'appleMusic', 597 playerUri: 598 'https://embed.music.apple.com/us/playlist/playlistName/playlistId', 599 }, 600 { 601 type: 'apple_music_album', 602 source: 'appleMusic', 603 playerUri: 'https://embed.music.apple.com/us/album/albumName/albumId', 604 }, 605 { 606 type: 'apple_music_song', 607 source: 'appleMusic', 608 playerUri: 609 'https://embed.music.apple.com/us/album/albumName/albumId?i=songId', 610 }, 611 612 { 613 type: 'vimeo_video', 614 source: 'vimeo', 615 playerUri: 'https://player.vimeo.com/video/videoId?autoplay=1', 616 }, 617 { 618 type: 'vimeo_video', 619 source: 'vimeo', 620 playerUri: 'https://player.vimeo.com/video/videoId?autoplay=1', 621 }, 622 623 { 624 type: 'giphy_gif', 625 source: 'giphy', 626 isGif: true, 627 hideDetails: true, 628 metaUri: 'https://giphy.com/gifs/gifId', 629 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 630 }, 631 undefined, 632 undefined, 633 { 634 type: 'giphy_gif', 635 source: 'giphy', 636 isGif: true, 637 hideDetails: true, 638 metaUri: 'https://giphy.com/gifs/39248209509382934029', 639 playerUri: 'https://i.giphy.com/media/39248209509382934029/200.webp', 640 }, 641 { 642 type: 'giphy_gif', 643 source: 'giphy', 644 isGif: true, 645 hideDetails: true, 646 metaUri: 'https://giphy.com/gifs/gifId', 647 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 648 }, 649 { 650 type: 'giphy_gif', 651 source: 'giphy', 652 isGif: true, 653 hideDetails: true, 654 metaUri: 'https://giphy.com/gifs/gifId', 655 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 656 }, 657 { 658 type: 'giphy_gif', 659 source: 'giphy', 660 isGif: true, 661 hideDetails: true, 662 metaUri: 'https://giphy.com/gifs/gifId', 663 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 664 }, 665 { 666 type: 'giphy_gif', 667 source: 'giphy', 668 isGif: true, 669 hideDetails: true, 670 metaUri: 'https://giphy.com/gifs/gifId', 671 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 672 }, 673 { 674 type: 'giphy_gif', 675 source: 'giphy', 676 isGif: true, 677 hideDetails: true, 678 metaUri: 'https://giphy.com/gifs/gifId', 679 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 680 }, 681 { 682 type: 'giphy_gif', 683 source: 'giphy', 684 isGif: true, 685 hideDetails: true, 686 metaUri: 'https://giphy.com/gifs/gifId', 687 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 688 }, 689 undefined, 690 undefined, 691 undefined, 692 693 { 694 type: 'giphy_gif', 695 source: 'giphy', 696 isGif: true, 697 hideDetails: true, 698 metaUri: 'https://giphy.com/gifs/gifId', 699 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 700 }, 701 702 { 703 type: 'giphy_gif', 704 source: 'giphy', 705 isGif: true, 706 hideDetails: true, 707 metaUri: 'https://giphy.com/gifs/gifId', 708 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 709 }, 710 { 711 type: 'giphy_gif', 712 source: 'giphy', 713 isGif: true, 714 hideDetails: true, 715 metaUri: 'https://giphy.com/gifs/gifId', 716 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 717 }, 718 { 719 type: 'giphy_gif', 720 source: 'giphy', 721 isGif: true, 722 hideDetails: true, 723 metaUri: 'https://giphy.com/gifs/gifId', 724 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 725 }, 726 { 727 type: 'giphy_gif', 728 source: 'giphy', 729 isGif: true, 730 hideDetails: true, 731 metaUri: 'https://giphy.com/gifs/gifId', 732 playerUri: 'https://i.giphy.com/media/gifId/200.webp', 733 }, 734 735 undefined, 736 undefined, 737 undefined, 738 undefined, 739 undefined, 740 741 { 742 type: 'tenor_gif', 743 source: 'tenor', 744 isGif: true, 745 hideDetails: true, 746 playerUri: 'https://t.gifs.bsky.app/someID_AAAAM/someName.gif', 747 dimensions: { 748 width: 100, 749 height: 100, 750 }, 751 }, 752 undefined, 753 undefined, 754 undefined, 755 undefined, 756 757 { 758 type: 'flickr_album', 759 source: 'flickr', 760 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 761 }, 762 { 763 type: 'flickr_album', 764 source: 'flickr', 765 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 766 }, 767 { 768 type: 'flickr_album', 769 source: 'flickr', 770 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 771 }, 772 { 773 type: 'flickr_album', 774 source: 'flickr', 775 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 776 }, 777 { 778 type: 'flickr_album', 779 source: 'flickr', 780 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661', 781 }, 782 783 undefined, 784 undefined, 785 undefined, 786 undefined, 787 788 { 789 type: 'flickr_album', 790 source: 'flickr', 791 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 792 }, 793 { 794 type: 'flickr_album', 795 source: 'flickr', 796 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 797 }, 798 { 799 type: 'flickr_album', 800 source: 'flickr', 801 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 802 }, 803 { 804 type: 'flickr_album', 805 source: 'flickr', 806 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 807 }, 808 { 809 type: 'flickr_album', 810 source: 'flickr', 811 playerUri: 'https://embedr.flickr.com/groups/898944@N23', 812 }, 813 814 undefined, 815 undefined, 816 ] 817 818 it('correctly grabs the correct id from uri', () => { 819 for (let i = 0; i < inputs.length; i++) { 820 const input = inputs[i] 821 const output = outputs[i] 822 823 const res = parseEmbedPlayerFromUrl(input) 824 825 expect(res).toEqual(output) 826 } 827 }) 828}) 829 830describe('createStarterPackLinkFromAndroidReferrer', () => { 831 const validOutput = 'at://haileyok.com/app.bsky.graph.starterpack/rkey' 832 833 it('returns a link when input contains utm_source and utm_content', () => { 834 expect( 835 createStarterPackLinkFromAndroidReferrer( 836 'utm_source=bluesky&utm_content=starterpack_haileyok.com_rkey', 837 ), 838 ).toEqual(validOutput) 839 840 expect( 841 createStarterPackLinkFromAndroidReferrer( 842 'utm_source=bluesky&utm_content=starterpack_test-lover-9000.com_rkey', 843 ), 844 ).toEqual('at://test-lover-9000.com/app.bsky.graph.starterpack/rkey') 845 }) 846 847 it('returns a link when input contains utm_source and utm_content in different order', () => { 848 expect( 849 createStarterPackLinkFromAndroidReferrer( 850 'utm_content=starterpack_haileyok.com_rkey&utm_source=bluesky', 851 ), 852 ).toEqual(validOutput) 853 }) 854 855 it('returns a link when input contains other parameters as well', () => { 856 expect( 857 createStarterPackLinkFromAndroidReferrer( 858 'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack_haileyok.com_rkey', 859 ), 860 ).toEqual(validOutput) 861 }) 862 863 it('returns null when utm_source is not present', () => { 864 expect( 865 createStarterPackLinkFromAndroidReferrer( 866 'utm_content=starterpack_haileyok.com_rkey', 867 ), 868 ).toEqual(null) 869 }) 870 871 it('returns null when utm_content is not present', () => { 872 expect( 873 createStarterPackLinkFromAndroidReferrer('utm_source=bluesky'), 874 ).toEqual(null) 875 }) 876 877 it('returns null when utm_content is malformed', () => { 878 expect( 879 createStarterPackLinkFromAndroidReferrer( 880 'utm_content=starterpack_haileyok.com', 881 ), 882 ).toEqual(null) 883 884 expect( 885 createStarterPackLinkFromAndroidReferrer('utm_content=starterpack'), 886 ).toEqual(null) 887 888 expect( 889 createStarterPackLinkFromAndroidReferrer( 890 'utm_content=starterpack_haileyok.com_rkey_more', 891 ), 892 ).toEqual(null) 893 894 expect( 895 createStarterPackLinkFromAndroidReferrer( 896 'utm_content=notastarterpack_haileyok.com_rkey', 897 ), 898 ).toEqual(null) 899 }) 900}) 901 902describe('parseStarterPackHttpUri', () => { 903 const baseUri = 'https://bsky.app/start' 904 905 it('returns a valid at uri when http uri is valid', () => { 906 const validHttpUri = `${baseUri}/haileyok.com/rkey` 907 expect(parseStarterPackUri(validHttpUri)).toEqual({ 908 name: 'haileyok.com', 909 rkey: 'rkey', 910 }) 911 912 const validHttpUri2 = `${baseUri}/haileyok.com/ilovetesting` 913 expect(parseStarterPackUri(validHttpUri2)).toEqual({ 914 name: 'haileyok.com', 915 rkey: 'ilovetesting', 916 }) 917 918 const validHttpUri3 = `${baseUri}/testlover9000.com/rkey` 919 expect(parseStarterPackUri(validHttpUri3)).toEqual({ 920 name: 'testlover9000.com', 921 rkey: 'rkey', 922 }) 923 }) 924 925 it('returns null when there is no rkey', () => { 926 const validHttpUri = `${baseUri}/haileyok.com` 927 expect(parseStarterPackUri(validHttpUri)).toEqual(null) 928 }) 929 930 it('returns null when there is an extra path', () => { 931 const validHttpUri = `${baseUri}/haileyok.com/rkey/other` 932 expect(parseStarterPackUri(validHttpUri)).toEqual(null) 933 }) 934 935 it('returns null when there is no handle or rkey', () => { 936 const validHttpUri = `${baseUri}` 937 expect(parseStarterPackUri(validHttpUri)).toEqual(null) 938 }) 939 940 it('returns null when the route is not /start or /starter-pack', () => { 941 const validHttpUri = 'https://bsky.app/start/haileyok.com/rkey' 942 expect(parseStarterPackUri(validHttpUri)).toEqual({ 943 name: 'haileyok.com', 944 rkey: 'rkey', 945 }) 946 947 const validHttpUri2 = 'https://bsky.app/starter-pack/haileyok.com/rkey' 948 expect(parseStarterPackUri(validHttpUri2)).toEqual({ 949 name: 'haileyok.com', 950 rkey: 'rkey', 951 }) 952 953 const invalidHttpUri = 'https://bsky.app/profile/haileyok.com/rkey' 954 expect(parseStarterPackUri(invalidHttpUri)).toEqual(null) 955 }) 956 957 it('returns the at uri when the input is a valid starterpack at uri', () => { 958 const validAtUri = 'at://did:123/app.bsky.graph.starterpack/rkey' 959 expect(parseStarterPackUri(validAtUri)).toEqual({ 960 name: 'did:123', 961 rkey: 'rkey', 962 }) 963 }) 964 965 it('returns null when the at uri has no rkey', () => { 966 const validAtUri = 'at://did:123/app.bsky.graph.starterpack' 967 expect(parseStarterPackUri(validAtUri)).toEqual(null) 968 }) 969 970 it('returns null when the collection is not app.bsky.graph.starterpack', () => { 971 const validAtUri = 'at://did:123/app.bsky.graph.list/rkey' 972 expect(parseStarterPackUri(validAtUri)).toEqual(null) 973 }) 974 975 it('returns null when the input is undefined', () => { 976 expect(parseStarterPackUri(undefined)).toEqual(null) 977 }) 978}) 979 980describe('createStarterPackGooglePlayUri', () => { 981 const base = 982 'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack_' 983 984 it('returns valid google play uri when input is valid', () => { 985 expect(createStarterPackGooglePlayUri('name', 'rkey')).toEqual( 986 `${base}name_rkey`, 987 ) 988 }) 989 990 it('returns null when no rkey is supplied', () => { 991 // @ts-expect-error test 992 expect(createStarterPackGooglePlayUri('name', undefined)).toEqual(null) 993 }) 994 995 it('returns null when no name or rkey are supplied', () => { 996 // @ts-expect-error test 997 expect(createStarterPackGooglePlayUri(undefined, undefined)).toEqual(null) 998 }) 999 1000 it('returns null when rkey is supplied but no name', () => { 1001 // @ts-expect-error test 1002 expect(createStarterPackGooglePlayUri(undefined, 'rkey')).toEqual(null) 1003 }) 1004}) 1005 1006describe('tenorUrlToBskyGifUrl', () => { 1007 const inputs = [ 1008 'https://media.tenor.com/someID_AAAAC/someName.gif', 1009 'https://media.tenor.com/someID/someName.gif', 1010 ] 1011 1012 it.each(inputs)( 1013 'returns url with t.gifs.bsky.app as hostname for input url', 1014 input => { 1015 const out = tenorUrlToBskyGifUrl(input) 1016 expect(out.startsWith('https://t.gifs.bsky.app/')).toEqual(true) 1017 }, 1018 ) 1019})