mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
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 = ['https://bsky.app', '/3jk7x4irgv52r', 'item/test/123']
271 const outputs = [
272 'https://bsky.app',
273 'https://bsky.app/3jk7x4irgv52r',
274 'https://bsky.app/item/test/123',
275 ]
276
277 it('appends https, when not present', () => {
278 for (let i = 0; i < inputs.length; i++) {
279 const result = toShareUrl(inputs[i])
280 expect(result).toEqual(outputs[i])
281 }
282 })
283})
284
285describe('shortenLinks', () => {
286 const inputs = [
287 'start https://middle.com/foo/bar?baz=bux#hash end',
288 'https://start.com/foo/bar?baz=bux#hash middle end',
289 'start middle https://end.com/foo/bar?baz=bux#hash',
290 'https://newline1.com/very/long/url/here\nhttps://newline2.com/very/long/url/here',
291 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
292 ]
293 const outputs = [
294 [
295 'start middle.com/foo/bar?baz=... end',
296 ['https://middle.com/foo/bar?baz=bux#hash'],
297 ],
298 [
299 'start.com/foo/bar?baz=... middle end',
300 ['https://start.com/foo/bar?baz=bux#hash'],
301 ],
302 [
303 'start middle end.com/foo/bar?baz=...',
304 ['https://end.com/foo/bar?baz=bux#hash'],
305 ],
306 [
307 'newline1.com/very/long/ur...\nnewline2.com/very/long/ur...',
308 [
309 'https://newline1.com/very/long/url/here',
310 'https://newline2.com/very/long/url/here',
311 ],
312 ],
313 [
314 'Classic article socket3.wordpress.com/2018/02/03/d...',
315 [
316 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
317 ],
318 ],
319 ]
320
321 it('correctly shortens rich text while preserving facet URIs', () => {
322 for (let i = 0; i < inputs.length; i++) {
323 const input = inputs[i]
324 const inputRT = new RichText({text: input})
325 inputRT.detectFacetsWithoutResolution()
326 const outputRT = shortenLinks(inputRT)
327 expect(outputRT.text).toEqual(outputs[i][0])
328 expect(outputRT.facets?.length).toEqual(outputs[i][1].length)
329 for (let j = 0; j < outputs[i][1].length; j++) {
330 expect(outputRT.facets![j].features[0].uri).toEqual(outputs[i][1][j])
331 }
332 }
333 })
334})
335
336describe('parseEmbedPlayerFromUrl', () => {
337 const inputs = [
338 'https://youtu.be/videoId',
339 'https://youtu.be/videoId?t=1s',
340 'https://www.youtube.com/watch?v=videoId',
341 'https://www.youtube.com/watch?v=videoId&feature=share',
342 'https://www.youtube.com/watch?v=videoId&t=1s',
343 'https://youtube.com/watch?v=videoId',
344 'https://youtube.com/watch?v=videoId&feature=share',
345 'https://youtube.com/shorts/videoId',
346 'https://youtube.com/live/videoId',
347 'https://m.youtube.com/watch?v=videoId',
348 'https://music.youtube.com/watch?v=videoId',
349
350 'https://youtube.com/shorts/',
351 'https://youtube.com/',
352 'https://youtube.com/random',
353 'https://youtube.com/live/',
354
355 'https://twitch.tv/channelName',
356 'https://www.twitch.tv/channelName',
357 'https://m.twitch.tv/channelName',
358
359 'https://twitch.tv/channelName/clip/clipId',
360 'https://twitch.tv/videos/videoId',
361
362 'https://open.spotify.com/playlist/playlistId',
363 'https://open.spotify.com/playlist/playlistId?param=value',
364 'https://open.spotify.com/locale/playlist/playlistId',
365
366 'https://open.spotify.com/track/songId',
367 'https://open.spotify.com/track/songId?param=value',
368 'https://open.spotify.com/locale/track/songId',
369
370 'https://open.spotify.com/album/albumId',
371 'https://open.spotify.com/album/albumId?param=value',
372 'https://open.spotify.com/locale/album/albumId',
373
374 'https://soundcloud.com/user/track',
375 'https://soundcloud.com/user/sets/set',
376 'https://soundcloud.com/user/',
377
378 'https://music.apple.com/us/playlist/playlistName/playlistId',
379 'https://music.apple.com/us/album/albumName/albumId',
380 'https://music.apple.com/us/album/albumName/albumId?i=songId',
381
382 'https://vimeo.com/videoId',
383 'https://vimeo.com/videoId?autoplay=0',
384
385 'https://giphy.com/gifs/some-random-gif-name-gifId',
386 'https://giphy.com/gif/some-random-gif-name-gifId',
387 'https://giphy.com/gifs/',
388
389 'https://giphy.com/gifs/39248209509382934029?hh=100&ww=100',
390
391 'https://media.giphy.com/media/gifId/giphy.webp',
392 'https://media0.giphy.com/media/gifId/giphy.webp',
393 'https://media1.giphy.com/media/gifId/giphy.gif',
394 'https://media2.giphy.com/media/gifId/giphy.webp',
395 'https://media3.giphy.com/media/gifId/giphy.mp4',
396 'https://media4.giphy.com/media/gifId/giphy.webp',
397 'https://media5.giphy.com/media/gifId/giphy.mp4',
398 'https://media0.giphy.com/media/gifId/giphy.mp3',
399 'https://media1.google.com/media/gifId/giphy.webp',
400
401 'https://media.giphy.com/media/trackingId/gifId/giphy.webp',
402
403 'https://i.giphy.com/media/gifId/giphy.webp',
404 'https://i.giphy.com/media/gifId/giphy.webp',
405 'https://i.giphy.com/gifId.gif',
406 'https://i.giphy.com/gifId.gif',
407
408 'https://tenor.com/view/gifId',
409 'https://tenor.com/notView/gifId',
410 'https://tenor.com/view',
411 'https://tenor.com/view/gifId.gif',
412 'https://tenor.com/intl/view/gifId.gif',
413
414 'https://media.tenor.com/someID_AAAAC/someName.gif?hh=100&ww=100',
415 'https://media.tenor.com/someID_AAAAC/someName.gif',
416 'https://media.tenor.com/someID/someName.gif',
417 'https://media.tenor.com/someID',
418 'https://media.tenor.com',
419
420 'https://www.flickr.com/photos/username/albums/72177720308493661',
421 'https://flickr.com/photos/username/albums/72177720308493661',
422 'https://flickr.com/photos/username/albums/72177720308493661/',
423 'https://flickr.com/photos/username/albums/72177720308493661//',
424 'https://flic.kr/s/aHBqjAES3i',
425
426 'https://flickr.com/foetoes/username/albums/3903',
427 'https://flickr.com/albums/3903',
428 'https://flic.kr/s/OolI',
429 'https://flic.kr/t/aHBqjAES3i',
430
431 'https://www.flickr.com/groups/898944@N23/pool',
432 'https://flickr.com/groups/898944@N23/pool',
433 'https://flickr.com/groups/898944@N23/pool/',
434 'https://flickr.com/groups/898944@N23/pool//',
435 'https://flic.kr/go/8WJtR',
436
437 'https://www.flickr.com/groups/898944@N23/',
438 'https://www.flickr.com/groups',
439 ]
440
441 const outputs = [
442 {
443 type: 'youtube_video',
444 source: 'youtube',
445 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
446 },
447 {
448 type: 'youtube_video',
449 source: 'youtube',
450 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=1',
451 },
452 {
453 type: 'youtube_video',
454 source: 'youtube',
455 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
456 },
457 {
458 type: 'youtube_video',
459 source: 'youtube',
460 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
461 },
462 {
463 type: 'youtube_video',
464 source: 'youtube',
465 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=1',
466 },
467 {
468 type: 'youtube_video',
469 source: 'youtube',
470 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
471 },
472 {
473 type: 'youtube_video',
474 source: 'youtube',
475 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
476 },
477 {
478 type: 'youtube_short',
479 source: 'youtubeShorts',
480 hideDetails: true,
481 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
482 },
483 {
484 type: 'youtube_video',
485 source: 'youtube',
486 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
487 },
488 {
489 type: 'youtube_video',
490 source: 'youtube',
491 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
492 },
493 {
494 type: 'youtube_video',
495 source: 'youtube',
496 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
497 },
498
499 undefined,
500 undefined,
501 undefined,
502 undefined,
503
504 {
505 type: 'twitch_video',
506 source: 'twitch',
507 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`,
508 },
509 {
510 type: 'twitch_video',
511 source: 'twitch',
512 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`,
513 },
514 {
515 type: 'twitch_video',
516 source: 'twitch',
517 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`,
518 },
519 {
520 type: 'twitch_video',
521 source: 'twitch',
522 playerUri: `https://clips.twitch.tv/embed?volume=0.5&autoplay=true&clip=clipId&parent=localhost`,
523 },
524 {
525 type: 'twitch_video',
526 source: 'twitch',
527 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&video=videoId&parent=localhost`,
528 },
529
530 {
531 type: 'spotify_playlist',
532 source: 'spotify',
533 playerUri: `https://open.spotify.com/embed/playlist/playlistId`,
534 },
535 {
536 type: 'spotify_playlist',
537 source: 'spotify',
538 playerUri: `https://open.spotify.com/embed/playlist/playlistId`,
539 },
540 {
541 type: 'spotify_playlist',
542 source: 'spotify',
543 playerUri: `https://open.spotify.com/embed/playlist/playlistId`,
544 },
545
546 {
547 type: 'spotify_song',
548 source: 'spotify',
549 playerUri: `https://open.spotify.com/embed/track/songId`,
550 },
551 {
552 type: 'spotify_song',
553 source: 'spotify',
554 playerUri: `https://open.spotify.com/embed/track/songId`,
555 },
556 {
557 type: 'spotify_song',
558 source: 'spotify',
559 playerUri: `https://open.spotify.com/embed/track/songId`,
560 },
561
562 {
563 type: 'spotify_album',
564 source: 'spotify',
565 playerUri: `https://open.spotify.com/embed/album/albumId`,
566 },
567 {
568 type: 'spotify_album',
569 source: 'spotify',
570 playerUri: `https://open.spotify.com/embed/album/albumId`,
571 },
572 {
573 type: 'spotify_album',
574 source: 'spotify',
575 playerUri: `https://open.spotify.com/embed/album/albumId`,
576 },
577
578 {
579 type: 'soundcloud_track',
580 source: 'soundcloud',
581 playerUri: `https://w.soundcloud.com/player/?url=https://soundcloud.com/user/track&auto_play=true&visual=false&hide_related=true`,
582 },
583 {
584 type: 'soundcloud_set',
585 source: 'soundcloud',
586 playerUri: `https://w.soundcloud.com/player/?url=https://soundcloud.com/user/sets/set&auto_play=true&visual=false&hide_related=true`,
587 },
588 undefined,
589
590 {
591 type: 'apple_music_playlist',
592 source: 'appleMusic',
593 playerUri:
594 'https://embed.music.apple.com/us/playlist/playlistName/playlistId',
595 },
596 {
597 type: 'apple_music_album',
598 source: 'appleMusic',
599 playerUri: 'https://embed.music.apple.com/us/album/albumName/albumId',
600 },
601 {
602 type: 'apple_music_song',
603 source: 'appleMusic',
604 playerUri:
605 'https://embed.music.apple.com/us/album/albumName/albumId?i=songId',
606 },
607
608 {
609 type: 'vimeo_video',
610 source: 'vimeo',
611 playerUri: 'https://player.vimeo.com/video/videoId?autoplay=1',
612 },
613 {
614 type: 'vimeo_video',
615 source: 'vimeo',
616 playerUri: 'https://player.vimeo.com/video/videoId?autoplay=1',
617 },
618
619 {
620 type: 'giphy_gif',
621 source: 'giphy',
622 isGif: true,
623 hideDetails: true,
624 metaUri: 'https://giphy.com/gifs/gifId',
625 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
626 },
627 undefined,
628 undefined,
629 {
630 type: 'giphy_gif',
631 source: 'giphy',
632 isGif: true,
633 hideDetails: true,
634 metaUri: 'https://giphy.com/gifs/39248209509382934029',
635 playerUri: 'https://i.giphy.com/media/39248209509382934029/200.webp',
636 },
637 {
638 type: 'giphy_gif',
639 source: 'giphy',
640 isGif: true,
641 hideDetails: true,
642 metaUri: 'https://giphy.com/gifs/gifId',
643 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
644 },
645 {
646 type: 'giphy_gif',
647 source: 'giphy',
648 isGif: true,
649 hideDetails: true,
650 metaUri: 'https://giphy.com/gifs/gifId',
651 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
652 },
653 {
654 type: 'giphy_gif',
655 source: 'giphy',
656 isGif: true,
657 hideDetails: true,
658 metaUri: 'https://giphy.com/gifs/gifId',
659 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
660 },
661 {
662 type: 'giphy_gif',
663 source: 'giphy',
664 isGif: true,
665 hideDetails: true,
666 metaUri: 'https://giphy.com/gifs/gifId',
667 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
668 },
669 {
670 type: 'giphy_gif',
671 source: 'giphy',
672 isGif: true,
673 hideDetails: true,
674 metaUri: 'https://giphy.com/gifs/gifId',
675 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
676 },
677 {
678 type: 'giphy_gif',
679 source: 'giphy',
680 isGif: true,
681 hideDetails: true,
682 metaUri: 'https://giphy.com/gifs/gifId',
683 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
684 },
685 undefined,
686 undefined,
687 undefined,
688
689 {
690 type: 'giphy_gif',
691 source: 'giphy',
692 isGif: true,
693 hideDetails: true,
694 metaUri: 'https://giphy.com/gifs/gifId',
695 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
696 },
697
698 {
699 type: 'giphy_gif',
700 source: 'giphy',
701 isGif: true,
702 hideDetails: true,
703 metaUri: 'https://giphy.com/gifs/gifId',
704 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
705 },
706 {
707 type: 'giphy_gif',
708 source: 'giphy',
709 isGif: true,
710 hideDetails: true,
711 metaUri: 'https://giphy.com/gifs/gifId',
712 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
713 },
714 {
715 type: 'giphy_gif',
716 source: 'giphy',
717 isGif: true,
718 hideDetails: true,
719 metaUri: 'https://giphy.com/gifs/gifId',
720 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
721 },
722 {
723 type: 'giphy_gif',
724 source: 'giphy',
725 isGif: true,
726 hideDetails: true,
727 metaUri: 'https://giphy.com/gifs/gifId',
728 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
729 },
730
731 undefined,
732 undefined,
733 undefined,
734 undefined,
735 undefined,
736
737 {
738 type: 'tenor_gif',
739 source: 'tenor',
740 isGif: true,
741 hideDetails: true,
742 playerUri: 'https://t.gifs.bsky.app/someID_AAAAM/someName.gif',
743 dimensions: {
744 width: 100,
745 height: 100,
746 },
747 },
748 undefined,
749 undefined,
750 undefined,
751 undefined,
752
753 {
754 type: 'flickr_album',
755 source: 'flickr',
756 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
757 },
758 {
759 type: 'flickr_album',
760 source: 'flickr',
761 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
762 },
763 {
764 type: 'flickr_album',
765 source: 'flickr',
766 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
767 },
768 {
769 type: 'flickr_album',
770 source: 'flickr',
771 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
772 },
773 {
774 type: 'flickr_album',
775 source: 'flickr',
776 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
777 },
778
779 undefined,
780 undefined,
781 undefined,
782 undefined,
783
784 {
785 type: 'flickr_album',
786 source: 'flickr',
787 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
788 },
789 {
790 type: 'flickr_album',
791 source: 'flickr',
792 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
793 },
794 {
795 type: 'flickr_album',
796 source: 'flickr',
797 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
798 },
799 {
800 type: 'flickr_album',
801 source: 'flickr',
802 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
803 },
804 {
805 type: 'flickr_album',
806 source: 'flickr',
807 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
808 },
809
810 undefined,
811 undefined,
812 ]
813
814 it('correctly grabs the correct id from uri', () => {
815 for (let i = 0; i < inputs.length; i++) {
816 const input = inputs[i]
817 const output = outputs[i]
818
819 const res = parseEmbedPlayerFromUrl(input)
820
821 expect(res).toEqual(output)
822 }
823 })
824})
825
826describe('createStarterPackLinkFromAndroidReferrer', () => {
827 const validOutput = 'at://haileyok.com/app.bsky.graph.starterpack/rkey'
828
829 it('returns a link when input contains utm_source and utm_content', () => {
830 expect(
831 createStarterPackLinkFromAndroidReferrer(
832 'utm_source=bluesky&utm_content=starterpack_haileyok.com_rkey',
833 ),
834 ).toEqual(validOutput)
835
836 expect(
837 createStarterPackLinkFromAndroidReferrer(
838 'utm_source=bluesky&utm_content=starterpack_test-lover-9000.com_rkey',
839 ),
840 ).toEqual('at://test-lover-9000.com/app.bsky.graph.starterpack/rkey')
841 })
842
843 it('returns a link when input contains utm_source and utm_content in different order', () => {
844 expect(
845 createStarterPackLinkFromAndroidReferrer(
846 'utm_content=starterpack_haileyok.com_rkey&utm_source=bluesky',
847 ),
848 ).toEqual(validOutput)
849 })
850
851 it('returns a link when input contains other parameters as well', () => {
852 expect(
853 createStarterPackLinkFromAndroidReferrer(
854 'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack_haileyok.com_rkey',
855 ),
856 ).toEqual(validOutput)
857 })
858
859 it('returns null when utm_source is not present', () => {
860 expect(
861 createStarterPackLinkFromAndroidReferrer(
862 'utm_content=starterpack_haileyok.com_rkey',
863 ),
864 ).toEqual(null)
865 })
866
867 it('returns null when utm_content is not present', () => {
868 expect(
869 createStarterPackLinkFromAndroidReferrer('utm_source=bluesky'),
870 ).toEqual(null)
871 })
872
873 it('returns null when utm_content is malformed', () => {
874 expect(
875 createStarterPackLinkFromAndroidReferrer(
876 'utm_content=starterpack_haileyok.com',
877 ),
878 ).toEqual(null)
879
880 expect(
881 createStarterPackLinkFromAndroidReferrer('utm_content=starterpack'),
882 ).toEqual(null)
883
884 expect(
885 createStarterPackLinkFromAndroidReferrer(
886 'utm_content=starterpack_haileyok.com_rkey_more',
887 ),
888 ).toEqual(null)
889
890 expect(
891 createStarterPackLinkFromAndroidReferrer(
892 'utm_content=notastarterpack_haileyok.com_rkey',
893 ),
894 ).toEqual(null)
895 })
896})
897
898describe('parseStarterPackHttpUri', () => {
899 const baseUri = 'https://bsky.app/start'
900
901 it('returns a valid at uri when http uri is valid', () => {
902 const validHttpUri = `${baseUri}/haileyok.com/rkey`
903 expect(parseStarterPackUri(validHttpUri)).toEqual({
904 name: 'haileyok.com',
905 rkey: 'rkey',
906 })
907
908 const validHttpUri2 = `${baseUri}/haileyok.com/ilovetesting`
909 expect(parseStarterPackUri(validHttpUri2)).toEqual({
910 name: 'haileyok.com',
911 rkey: 'ilovetesting',
912 })
913
914 const validHttpUri3 = `${baseUri}/testlover9000.com/rkey`
915 expect(parseStarterPackUri(validHttpUri3)).toEqual({
916 name: 'testlover9000.com',
917 rkey: 'rkey',
918 })
919 })
920
921 it('returns null when there is no rkey', () => {
922 const validHttpUri = `${baseUri}/haileyok.com`
923 expect(parseStarterPackUri(validHttpUri)).toEqual(null)
924 })
925
926 it('returns null when there is an extra path', () => {
927 const validHttpUri = `${baseUri}/haileyok.com/rkey/other`
928 expect(parseStarterPackUri(validHttpUri)).toEqual(null)
929 })
930
931 it('returns null when there is no handle or rkey', () => {
932 const validHttpUri = `${baseUri}`
933 expect(parseStarterPackUri(validHttpUri)).toEqual(null)
934 })
935
936 it('returns null when the route is not /start or /starter-pack', () => {
937 const validHttpUri = 'https://bsky.app/start/haileyok.com/rkey'
938 expect(parseStarterPackUri(validHttpUri)).toEqual({
939 name: 'haileyok.com',
940 rkey: 'rkey',
941 })
942
943 const validHttpUri2 = 'https://bsky.app/starter-pack/haileyok.com/rkey'
944 expect(parseStarterPackUri(validHttpUri2)).toEqual({
945 name: 'haileyok.com',
946 rkey: 'rkey',
947 })
948
949 const invalidHttpUri = 'https://bsky.app/profile/haileyok.com/rkey'
950 expect(parseStarterPackUri(invalidHttpUri)).toEqual(null)
951 })
952
953 it('returns the at uri when the input is a valid starterpack at uri', () => {
954 const validAtUri = 'at://did:123/app.bsky.graph.starterpack/rkey'
955 expect(parseStarterPackUri(validAtUri)).toEqual({
956 name: 'did:123',
957 rkey: 'rkey',
958 })
959 })
960
961 it('returns null when the at uri has no rkey', () => {
962 const validAtUri = 'at://did:123/app.bsky.graph.starterpack'
963 expect(parseStarterPackUri(validAtUri)).toEqual(null)
964 })
965
966 it('returns null when the collection is not app.bsky.graph.starterpack', () => {
967 const validAtUri = 'at://did:123/app.bsky.graph.list/rkey'
968 expect(parseStarterPackUri(validAtUri)).toEqual(null)
969 })
970
971 it('returns null when the input is undefined', () => {
972 expect(parseStarterPackUri(undefined)).toEqual(null)
973 })
974})
975
976describe('createStarterPackGooglePlayUri', () => {
977 const base =
978 'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack_'
979
980 it('returns valid google play uri when input is valid', () => {
981 expect(createStarterPackGooglePlayUri('name', 'rkey')).toEqual(
982 `${base}name_rkey`,
983 )
984 })
985
986 it('returns null when no rkey is supplied', () => {
987 // @ts-expect-error test
988 expect(createStarterPackGooglePlayUri('name', undefined)).toEqual(null)
989 })
990
991 it('returns null when no name or rkey are supplied', () => {
992 // @ts-expect-error test
993 expect(createStarterPackGooglePlayUri(undefined, undefined)).toEqual(null)
994 })
995
996 it('returns null when rkey is supplied but no name', () => {
997 // @ts-expect-error test
998 expect(createStarterPackGooglePlayUri(undefined, 'rkey')).toEqual(null)
999 })
1000})
1001
1002describe('tenorUrlToBskyGifUrl', () => {
1003 const inputs = [
1004 'https://media.tenor.com/someID_AAAAC/someName.gif',
1005 'https://media.tenor.com/someID/someName.gif',
1006 ]
1007
1008 it.each(inputs)(
1009 'returns url with t.gifs.bsky.app as hostname for input url',
1010 input => {
1011 const out = tenorUrlToBskyGifUrl(input)
1012 expect(out.startsWith('https://t.gifs.bsky.app/')).toEqual(true)
1013 },
1014 )
1015})