forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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 'https://music.apple.com/us/song/songName/songId',
382
383 'https://vimeo.com/videoId',
384 'https://vimeo.com/videoId?autoplay=0',
385
386 'https://giphy.com/gifs/some-random-gif-name-gifId',
387 'https://giphy.com/gif/some-random-gif-name-gifId',
388 'https://giphy.com/gifs/',
389
390 'https://giphy.com/gifs/39248209509382934029?hh=100&ww=100',
391
392 'https://media.giphy.com/media/gifId/giphy.webp',
393 'https://media0.giphy.com/media/gifId/giphy.webp',
394 'https://media1.giphy.com/media/gifId/giphy.gif',
395 'https://media2.giphy.com/media/gifId/giphy.webp',
396 'https://media3.giphy.com/media/gifId/giphy.mp4',
397 'https://media4.giphy.com/media/gifId/giphy.webp',
398 'https://media5.giphy.com/media/gifId/giphy.mp4',
399 'https://media0.giphy.com/media/gifId/giphy.mp3',
400 'https://media1.google.com/media/gifId/giphy.webp',
401
402 'https://media.giphy.com/media/trackingId/gifId/giphy.webp',
403
404 'https://i.giphy.com/media/gifId/giphy.webp',
405 'https://i.giphy.com/media/gifId/giphy.webp',
406 'https://i.giphy.com/gifId.gif',
407 'https://i.giphy.com/gifId.gif',
408
409 'https://tenor.com/view/gifId',
410 'https://tenor.com/notView/gifId',
411 'https://tenor.com/view',
412 'https://tenor.com/view/gifId.gif',
413 'https://tenor.com/intl/view/gifId.gif',
414
415 'https://media.tenor.com/someID_AAAAC/someName.gif?hh=100&ww=100',
416 'https://media.tenor.com/someID_AAAAC/someName.gif',
417 'https://media.tenor.com/someID/someName.gif',
418 'https://media.tenor.com/someID',
419 'https://media.tenor.com',
420
421 'https://www.flickr.com/photos/username/albums/72177720308493661',
422 'https://flickr.com/photos/username/albums/72177720308493661',
423 'https://flickr.com/photos/username/albums/72177720308493661/',
424 'https://flickr.com/photos/username/albums/72177720308493661//',
425 'https://flic.kr/s/aHBqjAES3i',
426
427 'https://flickr.com/foetoes/username/albums/3903',
428 'https://flickr.com/albums/3903',
429 'https://flic.kr/s/OolI',
430 'https://flic.kr/t/aHBqjAES3i',
431
432 'https://www.flickr.com/groups/898944@N23/pool',
433 'https://flickr.com/groups/898944@N23/pool',
434 'https://flickr.com/groups/898944@N23/pool/',
435 'https://flickr.com/groups/898944@N23/pool//',
436 'https://flic.kr/go/8WJtR',
437
438 'https://www.flickr.com/groups/898944@N23/',
439 'https://www.flickr.com/groups',
440
441 'https://maxblansjaar.bandcamp.com/album/false-comforts',
442 'https://grmnygrmny.bandcamp.com/track/fluid',
443 'https://sufjanstevens.bandcamp.com/',
444 'https://sufjanstevens.bandcamp.com',
445 'https://bandcamp.com/',
446 'https://bandcamp.com',
447 ]
448
449 const outputs = [
450 {
451 type: 'youtube_video',
452 source: 'youtube',
453 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
454 },
455 {
456 type: 'youtube_video',
457 source: 'youtube',
458 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=1',
459 },
460 {
461 type: 'youtube_video',
462 source: 'youtube',
463 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
464 },
465 {
466 type: 'youtube_video',
467 source: 'youtube',
468 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
469 },
470 {
471 type: 'youtube_video',
472 source: 'youtube',
473 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=1',
474 },
475 {
476 type: 'youtube_video',
477 source: 'youtube',
478 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
479 },
480 {
481 type: 'youtube_video',
482 source: 'youtube',
483 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
484 },
485 {
486 type: 'youtube_short',
487 source: 'youtubeShorts',
488 hideDetails: true,
489 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
490 },
491 {
492 type: 'youtube_video',
493 source: 'youtube',
494 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
495 },
496 {
497 type: 'youtube_video',
498 source: 'youtube',
499 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
500 },
501 {
502 type: 'youtube_video',
503 source: 'youtube',
504 playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0',
505 },
506
507 undefined,
508 undefined,
509 undefined,
510 undefined,
511
512 {
513 type: 'twitch_video',
514 source: 'twitch',
515 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`,
516 },
517 {
518 type: 'twitch_video',
519 source: 'twitch',
520 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`,
521 },
522 {
523 type: 'twitch_video',
524 source: 'twitch',
525 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=channelName&parent=localhost`,
526 },
527 {
528 type: 'twitch_video',
529 source: 'twitch',
530 playerUri: `https://clips.twitch.tv/embed?volume=0.5&autoplay=true&clip=clipId&parent=localhost`,
531 },
532 {
533 type: 'twitch_video',
534 source: 'twitch',
535 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&video=videoId&parent=localhost`,
536 },
537
538 {
539 type: 'spotify_playlist',
540 source: 'spotify',
541 playerUri: `https://open.spotify.com/embed/playlist/playlistId`,
542 },
543 {
544 type: 'spotify_playlist',
545 source: 'spotify',
546 playerUri: `https://open.spotify.com/embed/playlist/playlistId`,
547 },
548 {
549 type: 'spotify_playlist',
550 source: 'spotify',
551 playerUri: `https://open.spotify.com/embed/playlist/playlistId`,
552 },
553
554 {
555 type: 'spotify_song',
556 source: 'spotify',
557 playerUri: `https://open.spotify.com/embed/track/songId`,
558 },
559 {
560 type: 'spotify_song',
561 source: 'spotify',
562 playerUri: `https://open.spotify.com/embed/track/songId`,
563 },
564 {
565 type: 'spotify_song',
566 source: 'spotify',
567 playerUri: `https://open.spotify.com/embed/track/songId`,
568 },
569
570 {
571 type: 'spotify_album',
572 source: 'spotify',
573 playerUri: `https://open.spotify.com/embed/album/albumId`,
574 },
575 {
576 type: 'spotify_album',
577 source: 'spotify',
578 playerUri: `https://open.spotify.com/embed/album/albumId`,
579 },
580 {
581 type: 'spotify_album',
582 source: 'spotify',
583 playerUri: `https://open.spotify.com/embed/album/albumId`,
584 },
585
586 {
587 type: 'soundcloud_track',
588 source: 'soundcloud',
589 playerUri: `https://w.soundcloud.com/player/?url=https://soundcloud.com/user/track&auto_play=true&visual=false&hide_related=true`,
590 },
591 {
592 type: 'soundcloud_set',
593 source: 'soundcloud',
594 playerUri: `https://w.soundcloud.com/player/?url=https://soundcloud.com/user/sets/set&auto_play=true&visual=false&hide_related=true`,
595 },
596 undefined,
597
598 {
599 type: 'apple_music_playlist',
600 source: 'appleMusic',
601 playerUri:
602 'https://embed.music.apple.com/us/playlist/playlistName/playlistId',
603 },
604 {
605 type: 'apple_music_album',
606 source: 'appleMusic',
607 playerUri: 'https://embed.music.apple.com/us/album/albumName/albumId',
608 },
609 {
610 type: 'apple_music_song',
611 source: 'appleMusic',
612 playerUri:
613 'https://embed.music.apple.com/us/album/albumName/albumId?i=songId',
614 },
615 {
616 type: 'apple_music_song',
617 source: 'appleMusic',
618 playerUri: 'https://embed.music.apple.com/us/song/songName/songId',
619 },
620
621 {
622 type: 'vimeo_video',
623 source: 'vimeo',
624 playerUri: 'https://player.vimeo.com/video/videoId?autoplay=1',
625 },
626 {
627 type: 'vimeo_video',
628 source: 'vimeo',
629 playerUri: 'https://player.vimeo.com/video/videoId?autoplay=1',
630 },
631
632 {
633 type: 'giphy_gif',
634 source: 'giphy',
635 isGif: true,
636 hideDetails: true,
637 metaUri: 'https://giphy.com/gifs/gifId',
638 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
639 },
640 undefined,
641 undefined,
642 {
643 type: 'giphy_gif',
644 source: 'giphy',
645 isGif: true,
646 hideDetails: true,
647 metaUri: 'https://giphy.com/gifs/39248209509382934029',
648 playerUri: 'https://i.giphy.com/media/39248209509382934029/200.webp',
649 },
650 {
651 type: 'giphy_gif',
652 source: 'giphy',
653 isGif: true,
654 hideDetails: true,
655 metaUri: 'https://giphy.com/gifs/gifId',
656 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
657 },
658 {
659 type: 'giphy_gif',
660 source: 'giphy',
661 isGif: true,
662 hideDetails: true,
663 metaUri: 'https://giphy.com/gifs/gifId',
664 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
665 },
666 {
667 type: 'giphy_gif',
668 source: 'giphy',
669 isGif: true,
670 hideDetails: true,
671 metaUri: 'https://giphy.com/gifs/gifId',
672 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
673 },
674 {
675 type: 'giphy_gif',
676 source: 'giphy',
677 isGif: true,
678 hideDetails: true,
679 metaUri: 'https://giphy.com/gifs/gifId',
680 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
681 },
682 {
683 type: 'giphy_gif',
684 source: 'giphy',
685 isGif: true,
686 hideDetails: true,
687 metaUri: 'https://giphy.com/gifs/gifId',
688 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
689 },
690 {
691 type: 'giphy_gif',
692 source: 'giphy',
693 isGif: true,
694 hideDetails: true,
695 metaUri: 'https://giphy.com/gifs/gifId',
696 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
697 },
698 undefined,
699 undefined,
700 undefined,
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 {
712 type: 'giphy_gif',
713 source: 'giphy',
714 isGif: true,
715 hideDetails: true,
716 metaUri: 'https://giphy.com/gifs/gifId',
717 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
718 },
719 {
720 type: 'giphy_gif',
721 source: 'giphy',
722 isGif: true,
723 hideDetails: true,
724 metaUri: 'https://giphy.com/gifs/gifId',
725 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
726 },
727 {
728 type: 'giphy_gif',
729 source: 'giphy',
730 isGif: true,
731 hideDetails: true,
732 metaUri: 'https://giphy.com/gifs/gifId',
733 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
734 },
735 {
736 type: 'giphy_gif',
737 source: 'giphy',
738 isGif: true,
739 hideDetails: true,
740 metaUri: 'https://giphy.com/gifs/gifId',
741 playerUri: 'https://i.giphy.com/media/gifId/200.webp',
742 },
743
744 undefined,
745 undefined,
746 undefined,
747 undefined,
748 undefined,
749
750 {
751 type: 'tenor_gif',
752 source: 'tenor',
753 isGif: true,
754 hideDetails: true,
755 playerUri: 'https://t.gifs.bsky.app/someID_AAAAM/someName.gif',
756 dimensions: {
757 width: 100,
758 height: 100,
759 },
760 },
761 undefined,
762 undefined,
763 undefined,
764 undefined,
765
766 {
767 type: 'flickr_album',
768 source: 'flickr',
769 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
770 },
771 {
772 type: 'flickr_album',
773 source: 'flickr',
774 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
775 },
776 {
777 type: 'flickr_album',
778 source: 'flickr',
779 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
780 },
781 {
782 type: 'flickr_album',
783 source: 'flickr',
784 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
785 },
786 {
787 type: 'flickr_album',
788 source: 'flickr',
789 playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
790 },
791
792 undefined,
793 undefined,
794 undefined,
795 undefined,
796
797 {
798 type: 'flickr_album',
799 source: 'flickr',
800 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
801 },
802 {
803 type: 'flickr_album',
804 source: 'flickr',
805 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
806 },
807 {
808 type: 'flickr_album',
809 source: 'flickr',
810 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
811 },
812 {
813 type: 'flickr_album',
814 source: 'flickr',
815 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
816 },
817 {
818 type: 'flickr_album',
819 source: 'flickr',
820 playerUri: 'https://embedr.flickr.com/groups/898944@N23',
821 },
822
823 undefined,
824 undefined,
825
826 {
827 type: 'bandcamp_album',
828 source: 'bandcamp',
829 playerUri:
830 'https://bandcamp.com/EmbeddedPlayer/url=https%3A%2F%2Fmaxblansjaar.bandcamp.com%2Falbum%2Ffalse-comforts/size=large/bgcol=ffffff/linkcol=0687f5/minimal=true/transparent=true/',
831 },
832 {
833 type: 'bandcamp_track',
834 source: 'bandcamp',
835 playerUri:
836 'https://bandcamp.com/EmbeddedPlayer/url=https%3A%2F%2Fgrmnygrmny.bandcamp.com%2Ftrack%2Ffluid/size=large/bgcol=ffffff/linkcol=0687f5/minimal=true/transparent=true/',
837 },
838 undefined,
839 undefined,
840 undefined,
841 undefined,
842 ]
843
844 it('correctly grabs the correct id from uri', () => {
845 for (let i = 0; i < inputs.length; i++) {
846 const input = inputs[i]
847 const output = outputs[i]
848
849 const res = parseEmbedPlayerFromUrl(input)
850
851 expect(res).toEqual(output)
852 }
853 })
854})
855
856describe('createStarterPackLinkFromAndroidReferrer', () => {
857 const validOutput = 'at://haileyok.com/app.bsky.graph.starterpack/rkey'
858
859 it('returns a link when input contains utm_source and utm_content', () => {
860 expect(
861 createStarterPackLinkFromAndroidReferrer(
862 'utm_source=bluesky&utm_content=starterpack_haileyok.com_rkey',
863 ),
864 ).toEqual(validOutput)
865
866 expect(
867 createStarterPackLinkFromAndroidReferrer(
868 'utm_source=bluesky&utm_content=starterpack_test-lover-9000.com_rkey',
869 ),
870 ).toEqual('at://test-lover-9000.com/app.bsky.graph.starterpack/rkey')
871 })
872
873 it('returns a link when input contains utm_source and utm_content in different order', () => {
874 expect(
875 createStarterPackLinkFromAndroidReferrer(
876 'utm_content=starterpack_haileyok.com_rkey&utm_source=bluesky',
877 ),
878 ).toEqual(validOutput)
879 })
880
881 it('returns a link when input contains other parameters as well', () => {
882 expect(
883 createStarterPackLinkFromAndroidReferrer(
884 'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack_haileyok.com_rkey',
885 ),
886 ).toEqual(validOutput)
887 })
888
889 it('returns null when utm_source is not present', () => {
890 expect(
891 createStarterPackLinkFromAndroidReferrer(
892 'utm_content=starterpack_haileyok.com_rkey',
893 ),
894 ).toEqual(null)
895 })
896
897 it('returns null when utm_content is not present', () => {
898 expect(
899 createStarterPackLinkFromAndroidReferrer('utm_source=bluesky'),
900 ).toEqual(null)
901 })
902
903 it('returns null when utm_content is malformed', () => {
904 expect(
905 createStarterPackLinkFromAndroidReferrer(
906 'utm_content=starterpack_haileyok.com',
907 ),
908 ).toEqual(null)
909
910 expect(
911 createStarterPackLinkFromAndroidReferrer('utm_content=starterpack'),
912 ).toEqual(null)
913
914 expect(
915 createStarterPackLinkFromAndroidReferrer(
916 'utm_content=starterpack_haileyok.com_rkey_more',
917 ),
918 ).toEqual(null)
919
920 expect(
921 createStarterPackLinkFromAndroidReferrer(
922 'utm_content=notastarterpack_haileyok.com_rkey',
923 ),
924 ).toEqual(null)
925 })
926})
927
928describe('parseStarterPackHttpUri', () => {
929 const baseUri = 'https://bsky.app/start'
930
931 it('returns a valid at uri when http uri is valid', () => {
932 const validHttpUri = `${baseUri}/haileyok.com/rkey`
933 expect(parseStarterPackUri(validHttpUri)).toEqual({
934 name: 'haileyok.com',
935 rkey: 'rkey',
936 })
937
938 const validHttpUri2 = `${baseUri}/haileyok.com/ilovetesting`
939 expect(parseStarterPackUri(validHttpUri2)).toEqual({
940 name: 'haileyok.com',
941 rkey: 'ilovetesting',
942 })
943
944 const validHttpUri3 = `${baseUri}/testlover9000.com/rkey`
945 expect(parseStarterPackUri(validHttpUri3)).toEqual({
946 name: 'testlover9000.com',
947 rkey: 'rkey',
948 })
949 })
950
951 it('returns null when there is no rkey', () => {
952 const validHttpUri = `${baseUri}/haileyok.com`
953 expect(parseStarterPackUri(validHttpUri)).toEqual(null)
954 })
955
956 it('returns null when there is an extra path', () => {
957 const validHttpUri = `${baseUri}/haileyok.com/rkey/other`
958 expect(parseStarterPackUri(validHttpUri)).toEqual(null)
959 })
960
961 it('returns null when there is no handle or rkey', () => {
962 const validHttpUri = `${baseUri}`
963 expect(parseStarterPackUri(validHttpUri)).toEqual(null)
964 })
965
966 it('returns null when the route is not /start or /starter-pack', () => {
967 const validHttpUri = 'https://bsky.app/start/haileyok.com/rkey'
968 expect(parseStarterPackUri(validHttpUri)).toEqual({
969 name: 'haileyok.com',
970 rkey: 'rkey',
971 })
972
973 const validHttpUri2 = 'https://bsky.app/starter-pack/haileyok.com/rkey'
974 expect(parseStarterPackUri(validHttpUri2)).toEqual({
975 name: 'haileyok.com',
976 rkey: 'rkey',
977 })
978
979 const invalidHttpUri = 'https://bsky.app/profile/haileyok.com/rkey'
980 expect(parseStarterPackUri(invalidHttpUri)).toEqual(null)
981 })
982
983 it('returns the at uri when the input is a valid starterpack at uri', () => {
984 const validAtUri = 'at://did:plc:123/app.bsky.graph.starterpack/rkey'
985 expect(parseStarterPackUri(validAtUri)).toEqual({
986 name: 'did:plc:123',
987 rkey: 'rkey',
988 })
989 })
990
991 it('returns null when the at uri has no rkey', () => {
992 const validAtUri = 'at://did:plc:123/app.bsky.graph.starterpack'
993 expect(parseStarterPackUri(validAtUri)).toEqual(null)
994 })
995
996 it('returns null when the collection is not app.bsky.graph.starterpack', () => {
997 const validAtUri = 'at://did:plc:123/app.bsky.graph.list/rkey'
998 expect(parseStarterPackUri(validAtUri)).toEqual(null)
999 })
1000
1001 it('returns null when the input is undefined', () => {
1002 expect(parseStarterPackUri(undefined)).toEqual(null)
1003 })
1004})
1005
1006describe('createStarterPackGooglePlayUri', () => {
1007 const base =
1008 'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack_'
1009
1010 it('returns valid google play uri when input is valid', () => {
1011 expect(createStarterPackGooglePlayUri('name', 'rkey')).toEqual(
1012 `${base}name_rkey`,
1013 )
1014 })
1015
1016 it('returns null when no rkey is supplied', () => {
1017 // @ts-expect-error test
1018 expect(createStarterPackGooglePlayUri('name', undefined)).toEqual(null)
1019 })
1020
1021 it('returns null when no name or rkey are supplied', () => {
1022 // @ts-expect-error test
1023 expect(createStarterPackGooglePlayUri(undefined, undefined)).toEqual(null)
1024 })
1025
1026 it('returns null when rkey is supplied but no name', () => {
1027 // @ts-expect-error test
1028 expect(createStarterPackGooglePlayUri(undefined, 'rkey')).toEqual(null)
1029 })
1030})
1031
1032describe('tenorUrlToBskyGifUrl', () => {
1033 const inputs = [
1034 'https://media.tenor.com/someID_AAAAC/someName.gif',
1035 'https://media.tenor.com/someID/someName.gif',
1036 ]
1037
1038 it.each(inputs)(
1039 'returns url with t.gifs.bsky.app as hostname for input url',
1040 input => {
1041 const out = tenorUrlToBskyGifUrl(input)
1042 expect(out.startsWith('https://t.gifs.bsky.app/')).toEqual(true)
1043 },
1044 )
1045})