likes.test.js
1 import { describe, expect, it } from 'vitest'; 2 import { __test__ } from './likes.js'; 3 describe('twitter likes helpers', () => { 4 it('falls back when queryId contains unsafe characters', () => { 5 expect(__test__.sanitizeQueryId('safe_Query-123', 'fallback')).toBe('safe_Query-123'); 6 expect(__test__.sanitizeQueryId('bad"id', 'fallback')).toBe('fallback'); 7 expect(__test__.sanitizeQueryId('bad/id', 'fallback')).toBe('fallback'); 8 expect(__test__.sanitizeQueryId(null, 'fallback')).toBe('fallback'); 9 }); 10 it('builds likes url with the provided queryId', () => { 11 const url = __test__.buildLikesUrl('query123', '42', 20, 'cursor-1'); 12 expect(url).toContain('/i/api/graphql/query123/Likes'); 13 expect(decodeURIComponent(url)).toContain('"userId":"42"'); 14 expect(decodeURIComponent(url)).toContain('"cursor":"cursor-1"'); 15 }); 16 it('parses likes timeline entries and bottom cursor', () => { 17 const payload = { 18 data: { 19 user: { 20 result: { 21 timeline_v2: { 22 timeline: { 23 instructions: [ 24 { 25 entries: [ 26 { 27 entryId: 'tweet-1', 28 content: { 29 itemContent: { 30 tweet_results: { 31 result: { 32 rest_id: '1', 33 legacy: { 34 full_text: 'liked post', 35 favorite_count: 7, 36 retweet_count: 2, 37 created_at: 'now', 38 }, 39 core: { 40 user_results: { 41 result: { 42 legacy: { 43 screen_name: 'alice', 44 name: 'Alice', 45 }, 46 }, 47 }, 48 }, 49 }, 50 }, 51 }, 52 }, 53 }, 54 { 55 entryId: 'cursor-bottom-1', 56 content: { 57 entryType: 'TimelineTimelineCursor', 58 cursorType: 'Bottom', 59 value: 'cursor-next', 60 }, 61 }, 62 ], 63 }, 64 ], 65 }, 66 }, 67 }, 68 }, 69 }, 70 }; 71 const result = __test__.parseLikes(payload, new Set()); 72 expect(result.nextCursor).toBe('cursor-next'); 73 expect(result.tweets).toHaveLength(1); 74 expect(result.tweets[0]).toMatchObject({ 75 id: '1', 76 author: 'alice', 77 name: 'Alice', 78 text: 'liked post', 79 likes: 7, 80 retweets: 2, 81 created_at: 'now', 82 url: 'https://x.com/alice/status/1', 83 }); 84 }); 85 });