test(js): add node:test unit tests for countdown and pleroma adapter
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Unit tests for js/pleroma-adapter.js.
|
||||
*
|
||||
* The adapter is an IIFE that wraps window.fetch. It is loaded in a vm
|
||||
* context with a mocked window object, so tests can call the wrapped
|
||||
* fetch and inspect how Pleroma API responses are mapped to the
|
||||
* Mastodon format expected by mastodon-timeline.umd.js.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const ADAPTER_PATH = path.join(__dirname, '..', '..', 'js', 'pleroma-adapter.js');
|
||||
const ADAPTER_SOURCE = fs.readFileSync(ADAPTER_PATH, 'utf8');
|
||||
|
||||
const TIMELINE_URL = 'https://pleroma.example/api/v1/timelines/public';
|
||||
const ACCOUNT_STATUSES_URL = 'https://pleroma.example/api/v1/accounts/42/statuses';
|
||||
|
||||
/**
|
||||
* Loads the adapter in an isolated vm context.
|
||||
*
|
||||
* @param {Function} handler Fake backend: receives the URL, returns a Response
|
||||
* @returns {{fetch: Function, warnings: string[]}} The wrapped fetch and logged warnings
|
||||
*/
|
||||
function loadAdapter(handler) {
|
||||
const warnings = [];
|
||||
const windowMock = {
|
||||
fetch: async (url) => handler(url)
|
||||
};
|
||||
|
||||
const sandbox = {
|
||||
window: windowMock,
|
||||
Response,
|
||||
console: {
|
||||
log() {},
|
||||
warn: (...args) => warnings.push(args.join(' ')),
|
||||
error() {}
|
||||
}
|
||||
};
|
||||
|
||||
vm.createContext(sandbox);
|
||||
vm.runInContext(ADAPTER_SOURCE, sandbox);
|
||||
|
||||
return { fetch: windowMock.fetch, warnings };
|
||||
}
|
||||
|
||||
function jsonResponse(data, init = {}) {
|
||||
return new Response(JSON.stringify(data), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
...init
|
||||
});
|
||||
}
|
||||
|
||||
describe('Pleroma adapter', () => {
|
||||
test('adds default meta to timeline attachments missing meta', async () => {
|
||||
const posts = [
|
||||
{
|
||||
id: '1',
|
||||
content: 'hello',
|
||||
media_attachments: [
|
||||
{ id: 'a1', type: 'image', url: 'https://pleroma.example/img.png' }
|
||||
]
|
||||
},
|
||||
{ id: '2', content: 'no media' }
|
||||
];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
const data = await res.json();
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(data.length, 2);
|
||||
|
||||
const attachment = data[0].media_attachments[0];
|
||||
assert.deepStrictEqual(attachment.meta.original, {
|
||||
width: 1280,
|
||||
height: 720,
|
||||
aspect: 1280 / 720
|
||||
});
|
||||
assert.deepStrictEqual(attachment.meta.small, {
|
||||
width: 640,
|
||||
height: 360,
|
||||
aspect: 1280 / 720
|
||||
});
|
||||
// Other fields are preserved
|
||||
assert.strictEqual(attachment.url, 'https://pleroma.example/img.png');
|
||||
assert.strictEqual(data[0].content, 'hello');
|
||||
assert.strictEqual(data[1].content, 'no media');
|
||||
assert.strictEqual(data[1].media_attachments, undefined);
|
||||
});
|
||||
|
||||
test('uses 1920x1080 for video attachments (pleroma mime_type)', async () => {
|
||||
const posts = [
|
||||
{
|
||||
id: '9',
|
||||
media_attachments: [
|
||||
{ id: 'v1', pleroma: { mime_type: 'video/mp4' } }
|
||||
]
|
||||
}
|
||||
];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts));
|
||||
|
||||
const res = await fetch(ACCOUNT_STATUSES_URL);
|
||||
const data = await res.json();
|
||||
|
||||
const meta = data[0].media_attachments[0].meta;
|
||||
assert.deepStrictEqual(meta.original, {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
aspect: 1920 / 1080
|
||||
});
|
||||
assert.deepStrictEqual(meta.small, {
|
||||
width: 960,
|
||||
height: 540,
|
||||
aspect: 1920 / 1080
|
||||
});
|
||||
});
|
||||
|
||||
test('uses 1200x800 for image attachments (pleroma mime_type)', async () => {
|
||||
const post = {
|
||||
id: '10',
|
||||
media_attachments: [
|
||||
{ id: 'i1', pleroma: { mime_type: 'image/jpeg' } }
|
||||
]
|
||||
};
|
||||
const { fetch } = loadAdapter(() => jsonResponse(post));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
const data = await res.json();
|
||||
|
||||
const meta = data.media_attachments[0].meta;
|
||||
assert.deepStrictEqual(meta.original, {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
aspect: 1200 / 800
|
||||
});
|
||||
assert.deepStrictEqual(meta.small, {
|
||||
width: 600,
|
||||
height: 400,
|
||||
aspect: 1200 / 800
|
||||
});
|
||||
});
|
||||
|
||||
test('adapts a single (non-array) post object', async () => {
|
||||
const post = {
|
||||
id: 'single',
|
||||
media_attachments: [{ id: 's1' }]
|
||||
};
|
||||
const { fetch } = loadAdapter(() => jsonResponse(post));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
const data = await res.json();
|
||||
|
||||
assert.strictEqual(Array.isArray(data), false);
|
||||
assert.strictEqual(data.id, 'single');
|
||||
assert.deepStrictEqual(data.media_attachments[0].meta.original, {
|
||||
width: 1280,
|
||||
height: 720,
|
||||
aspect: 1280 / 720
|
||||
});
|
||||
});
|
||||
|
||||
test('leaves attachments with complete meta untouched', async () => {
|
||||
const completeMeta = {
|
||||
original: { width: 640, height: 480, aspect: 640 / 480 },
|
||||
small: { width: 320, height: 240, aspect: 640 / 480 }
|
||||
};
|
||||
const posts = [
|
||||
{ id: 'm1', media_attachments: [{ id: 'ok', meta: completeMeta }] }
|
||||
];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
const data = await res.json();
|
||||
|
||||
assert.deepStrictEqual(data[0].media_attachments[0].meta, completeMeta);
|
||||
});
|
||||
|
||||
test('keeps existing partial meta.original and fills meta.small', async () => {
|
||||
const posts = [
|
||||
{
|
||||
id: 'p1',
|
||||
media_attachments: [
|
||||
{
|
||||
id: 'partial',
|
||||
meta: { original: { width: 640, height: 480, aspect: 640 / 480 } }
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
const data = await res.json();
|
||||
|
||||
const meta = data[0].media_attachments[0].meta;
|
||||
assert.deepStrictEqual(meta.original, { width: 640, height: 480, aspect: 640 / 480 });
|
||||
// meta.small is created from the default dimensions
|
||||
assert.deepStrictEqual(meta.small, {
|
||||
width: 640,
|
||||
height: 360,
|
||||
aspect: 1280 / 720
|
||||
});
|
||||
});
|
||||
|
||||
test('adapts media attachments inside reblogs recursively', async () => {
|
||||
const posts = [
|
||||
{
|
||||
id: 'r1',
|
||||
reblog: {
|
||||
id: 'r2',
|
||||
media_attachments: [
|
||||
{ id: 'rb', pleroma: { mime_type: 'image/png' } }
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
const data = await res.json();
|
||||
|
||||
const meta = data[0].reblog.media_attachments[0].meta;
|
||||
assert.deepStrictEqual(meta.original, {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
aspect: 1200 / 800
|
||||
});
|
||||
});
|
||||
|
||||
test('does not mutate the original payload objects', async () => {
|
||||
const attachment = { id: 'orig' };
|
||||
const posts = [{ id: 'n1', media_attachments: [attachment] }];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
await res.json();
|
||||
|
||||
assert.strictEqual(attachment.meta, undefined);
|
||||
assert.deepStrictEqual(posts[0].media_attachments, [attachment]);
|
||||
});
|
||||
|
||||
test('passes non-object array entries through unchanged', async () => {
|
||||
const posts = [null, 'not-a-post', { id: 'real', media_attachments: [{ id: 'x' }] }];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
const data = await res.json();
|
||||
|
||||
assert.strictEqual(data[0], null);
|
||||
assert.strictEqual(data[1], 'not-a-post');
|
||||
assert.ok(data[2].media_attachments[0].meta);
|
||||
});
|
||||
|
||||
test('intercepts account statuses URLs only when they contain /statuses', async () => {
|
||||
const posts = [{ id: 'a', media_attachments: [{ id: 'x' }] }];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts));
|
||||
|
||||
// /api/v1/accounts/42/statuses is adapted
|
||||
const resStatuses = await fetch(ACCOUNT_STATUSES_URL);
|
||||
const dataStatuses = await resStatuses.json();
|
||||
assert.ok(dataStatuses[0].media_attachments[0].meta);
|
||||
|
||||
// /api/v1/accounts/42 (no /statuses) is left as-is
|
||||
const resAccount = await fetch('https://pleroma.example/api/v1/accounts/42');
|
||||
const dataAccount = await resAccount.json();
|
||||
assert.strictEqual(dataAccount[0].media_attachments[0].meta, undefined);
|
||||
});
|
||||
|
||||
test('returns non-API responses untouched (same Response object)', async () => {
|
||||
let original;
|
||||
const { fetch } = loadAdapter(() => {
|
||||
original = jsonResponse({ ok: true });
|
||||
return original;
|
||||
});
|
||||
|
||||
const res = await fetch('https://example.com/about');
|
||||
|
||||
assert.strictEqual(res, original);
|
||||
assert.deepStrictEqual(await res.json(), { ok: true });
|
||||
});
|
||||
|
||||
test('falls back to the original response when JSON parsing fails', async () => {
|
||||
let original;
|
||||
const { fetch, warnings } = loadAdapter(() => {
|
||||
original = new Response('not-json', { status: 200 });
|
||||
return original;
|
||||
});
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
|
||||
assert.strictEqual(res, original);
|
||||
assert.strictEqual(await res.text(), 'not-json');
|
||||
assert.strictEqual(warnings.length, 1);
|
||||
});
|
||||
|
||||
test('preserves response status on adapted responses', async () => {
|
||||
const posts = [{ id: 's', media_attachments: [] }];
|
||||
const { fetch } = loadAdapter(() => jsonResponse(posts, { status: 201 }));
|
||||
|
||||
const res = await fetch(TIMELINE_URL);
|
||||
|
||||
assert.strictEqual(res.status, 201);
|
||||
assert.deepStrictEqual(await res.json(), posts);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user