import assert from 'node:assert/strict'; import { test } from 'node:test'; import { mediaFromDetails, quotedFrom, toPost } from '../src/platforms/x.ts'; import { fixture } from './helpers.ts'; const URL_ = 'https://x.com/example/status/1'; test('a text-only post carries no media', () => { const post = toPost(fixture('x/text-only.json'), URL_); assert.equal(post.platform, 'x'); assert.equal(post.textPosition, 'above'); assert.deepEqual(post.segments[0]?.media, []); assert.equal(post.author.handle, '@jack'); assert.ok(post.segments[0]?.postedAt); }); test('a photo post keeps every photo and asks for the original size', () => { const media = toPost(fixture('x/photo.json'), URL_).segments[0]?.media ?? []; assert.ok(media.length > 1, 'expected more than one photo'); assert.ok(media.every((m) => m.kind === 'image')); assert.ok(media.every((m) => m.url.endsWith('?name=orig'))); }); test('a video picks the highest-bitrate mp4 and ignores the streaming variants', () => { const media = mediaFromDetails([ { type: 'video', media_url_https: 'https://pbs.twimg.com/poster.jpg', original_info: { w: 1280, h: 720 }, video_info: { duration_millis: 30_500, variants: [ { content_type: 'application/x-mpegURL', url: 'https://video.twimg.com/p.m3u8' }, { content_type: 'video/mp4', bitrate: 832_000, url: 'https://video.twimg.com/low.mp4' }, { content_type: 'video/mp4', bitrate: 2_176_000, url: 'https://video.twimg.com/high.mp4' }, ], }, }, ]); assert.deepEqual(media, [ { kind: 'video', url: 'https://video.twimg.com/high.mp4', poster: { url: 'https://pbs.twimg.com/poster.jpg' }, durationSec: 31, width: 1280, height: 720, }, ]); }); test('a quote post carries the post it quotes, whose it is included', () => { // Someone continuing their own thought from an earlier post. Without the // quoted half on the page the remaining half says nothing. const post = toPost(fixture('x/quote-text.json'), URL_); const quoted = post.segments[0]?.quoted; assert.ok(quoted, 'the quoted post must survive'); assert.equal(quoted?.author.handle, '@MikeMcMahonCHN'); assert.ok(quoted?.author.displayName); assert.match(quoted?.text ?? '', /Matthew Mayich/); assert.ok(quoted?.postedAt); // No permalink in the payload, so it is rebuilt from the handle and the id. assert.equal(quoted?.url, 'https://x.com/MikeMcMahonCHN/status/2092384930034548928'); }); test('the quoted post keeps its own pictures instead of lending them to the quoter', () => { const post = toPost(fixture('x/quote-photo.json'), URL_); assert.deepEqual(post.segments[0]?.media, [], 'the quoter attached nothing'); assert.equal(post.segments[0]?.quoted?.media.length, 1); assert.equal(post.segments[0]?.quoted?.media[0]?.kind, 'image'); }); test('media on both sides stays on the side it came from', () => { // This is the case the old either-or could not represent at all: it showed // the quoter's picture and silently dropped the one being talked about. const segment = toPost(fixture('x/quote-both.json'), URL_).segments[0]; assert.equal(segment?.media.length, 1); assert.equal(segment?.quoted?.media.length, 1); assert.notEqual(segment?.media[0]?.url, segment?.quoted?.media[0]?.url); }); test('a post quoting nothing has no quoted post', () => { assert.equal(toPost(fixture('x/photo.json'), URL_).segments[0]?.quoted, undefined); assert.equal(quotedFrom(undefined), undefined); // A quoted post X will not describe is nothing to show. assert.equal(quotedFrom({ text: 'orphaned' }), undefined); }); test('the shortlink X staples to a quote post is not shown', () => { // The quoted post is right there on the page; a t.co pointing at it is // noise, which is why X hides it too. `display_text_range` says where it // starts, and keeps a link the author put there deliberately. const both = toPost(fixture('x/quote-both.json'), URL_).segments[0]; // Two links arrive: one the author wrote, one X appended pointing at the // quoted post. Exactly the second goes. assert.equal((both?.text?.match(/https?:\/\//g) ?? []).length, 1, "the author's own link stays"); assert.match(both?.text ?? '', /on3\.com/); assert.ok(!both?.text?.includes('QdJhOVu4En')); const photo = toPost(fixture('x/quote-photo.json'), URL_).segments[0]; assert.ok(!photo?.quoted?.text?.includes('t.co'), 'and the same on the quoted post'); }); test('a t.co stands aside for the address it stands in for', () => { // Left alone the page says `t.co/rEGx9JN5pJ`, which tells the reader // nothing and sends them through X's click tracker to find out. const segment = toPost(fixture('x/quote-both.json'), URL_).segments[0]; assert.ok(!segment?.text?.includes('t.co/'), `still shortened: ${segment?.text}`); assert.match(segment?.text ?? '', /on3\.com/); assert.match(segment?.quoted?.text ?? '', /on3\.com/); }); test('a shortlink the payload does not explain is left as it is', () => { const post = toPost( { text: 'see https://t.co/unknown1 and https://t.co/known123', user: { screen_name: 'someone' }, entities: { urls: [{ url: 'https://t.co/known123', expanded_url: 'https://example.com/real' }] }, }, URL_, ); assert.equal(post.segments[0]?.text, 'see https://t.co/unknown1 and https://example.com/real'); }); test('an expansion that is not an http address is refused', () => { const post = toPost( { text: 'https://t.co/abc', user: { screen_name: 'someone' }, entities: { urls: [{ url: 'https://t.co/abc', expanded_url: 'javascript:alert(1)' }] }, }, URL_, ); assert.equal(post.segments[0]?.text, 'https://t.co/abc'); }); test('the visible range is applied before the entities are decoded', () => { // The indices are into the escaped text, where `&` is five characters. // Decoding first shifts everything after it and truncates the tail. const post = toPost( { text: 'Tom & Jerry https://t.co/xxx', display_text_range: [0, 15], user: { screen_name: 'someone' }, }, URL_, ); assert.equal(post.segments[0]?.text, 'Tom & Jerry'); }); test('escaped characters X hands back are decoded, not shown as entities', () => { // The syndication payload arrives pre-escaped; everything downstream // escapes again on the way out, so leaving these puts `&` on screen. const post = toPost( { text: 'Tom & Jerry <3 "quoted"', user: { screen_name: 'someone' }, quoted_tweet: { text: 'me & you', user: { screen_name: 'other' }, id_str: '9' }, }, URL_, ); assert.equal(post.segments[0]?.text, 'Tom & Jerry <3 "quoted"'); assert.equal(post.segments[0]?.quoted?.text, 'me & you'); });