import { fromJS } from 'immutable'; import { normalizeStatus } from '@/mastodon/actions/importer/statuses'; import type { ApiAudioAttachmentJSON, ApiGifvAttachmentJSON, ApiImageAttachmentJSON, ApiMediaAttachmentJSON, ApiVideoAttachmentJSON, BaseApiMediaAttachmentJSON, } from '@/mastodon/api_types/media_attachments'; import type { ApiPollJSON } from '@/mastodon/api_types/polls'; import type { ApiRelationshipJSON } from '@/mastodon/api_types/relationships'; import type { ApiStatusJSON } from '@/mastodon/api_types/statuses'; import type { CustomEmojiData, UnicodeEmojiData, } from '@/mastodon/features/emoji/types'; import { createAccountFromServerJSON } from '@/mastodon/models/account'; import type { AnnualReport } from '@/mastodon/models/annual_report'; import type { Status } from '@/mastodon/models/status'; import type { DeepPartial } from '@/mastodon/utils/types'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; type FactoryOptions = { id?: string; } & Partial; type FactoryFunction = (options?: FactoryOptions) => T; export const accountFactory: FactoryFunction = ({ id, ...data } = {}) => ({ id: id ?? '1', acct: 'testuser', avatar: '/avatars/original/missing.png', avatar_static: '/avatars/original/missing.png', avatar_description: '', username: 'testuser', display_name: 'Test User', bot: false, created_at: '2023-01-01T00:00:00.000Z', discoverable: true, emojis: [], feature_approval: { automatic: [], manual: [], current_user: 'missing', }, fields: [], followers_count: 0, following_count: 0, group: false, header: '/header.png', header_static: '/header_static.png', header_description: '', indexable: true, last_status_at: '2023-01-01', locked: false, show_featured: true, show_media: true, show_media_replies: true, mute_expires_at: null, note: 'This is a test user account.', statuses_count: 0, suspended: false, url: '/@testuser', uri: '/users/testuser', noindex: false, roles: [], hide_collections: false, ...data, }); export const accountFactoryState = ( options: FactoryOptions = {}, ) => createAccountFromServerJSON(accountFactory(options)); export const statusFactory: FactoryFunction = ({ id, ...data } = {}) => ({ id: id ?? '1', created_at: '2023-01-01T00:00:00.000Z', sensitive: false, visibility: 'public', language: 'en', uri: 'https://example.com/status/1', url: 'https://example.com/status/1', replies_count: 0, reblogs_count: 0, quotes_count: 0, favourites_count: 0, account: accountFactory(), media_attachments: [], mentions: [], tags: [], emojis: [], tagged_collections: [], content: data.text ?.split('\n') .map((line) => `

${line}

`) .join('\n') ?? '

This is a test status.

', ...data, }); export const statusFactoryState = ( options: FactoryOptions = {}, ) => fromJS(normalizeStatus(statusFactory(options))) as unknown as Status; const baseAttachment = { id: '1', url: 'https://example.com/image/1', preview_url: 'https://example.com/image/1/preview', blurhash: '', } as const; const imageMeta = { width: 100, height: 100, aspect: 1, size: '100x100', } as const; const videoMeta = { width: 100, height: 100, frame_rate: '24', duration: 120, bitrate: 100, } as const; const colorsMeta = { background: '#ffffff', foreground: '#000000', accent: '#ff0000', } as const; type MediaFactoryArg = Omit< DeepPartial, 'type' >; export const imageAttachmentFactory = ( data: MediaFactoryArg = {}, ): ApiImageAttachmentJSON => ({ ...baseAttachment, ...data, type: 'image', meta: { original: { ...imageMeta, ...data.meta?.original }, small: { ...imageMeta, ...data.meta?.small }, }, }); export const videoAttachmentFactory = ( data: MediaFactoryArg = {}, ): ApiVideoAttachmentJSON => ({ ...baseAttachment, ...data, type: 'video', meta: { colors: { ...colorsMeta, ...data.meta?.colors }, original: { ...videoMeta, ...data.meta?.original }, small: { ...imageMeta, ...data.meta?.small }, focus: { x: 0, y: 0, ...data.meta?.focus, }, }, }); export const audioAttachmentFactory = ( data: MediaFactoryArg = {}, ): ApiAudioAttachmentJSON => ({ ...baseAttachment, ...data, type: 'audio', meta: { colors: { ...colorsMeta, ...data.meta?.colors }, original: { ...videoMeta, ...data.meta?.original }, small: { ...imageMeta, ...data.meta?.small }, }, }); export const gifvAttachmentFactory = ( data: MediaFactoryArg = {}, ): ApiGifvAttachmentJSON => ({ ...baseAttachment, ...data, type: 'gifv', meta: { original: { ...videoMeta, ...data.meta?.original }, small: { ...imageMeta, ...data.meta?.small }, }, }); export function mediaAttachmentFactory( data: DeepPartial = {}, ): ApiMediaAttachmentJSON { switch (data.type ?? 'image') { case 'image': return imageAttachmentFactory( data as DeepPartial, ); case 'video': return videoAttachmentFactory( data as DeepPartial, ); case 'audio': return audioAttachmentFactory( data as DeepPartial, ); case 'gifv': return gifvAttachmentFactory(data as DeepPartial); default: { return { ...baseAttachment, meta: {}, ...data, type: 'unknown', }; } } } export const pollFactory: FactoryFunction = (data = {}) => ({ id: '1', expires_at: '', expired: false, multiple: false, voters_count: 0, votes_count: 0, voted: false, options: [ { title: 'Option 1', votes_count: 0, }, { title: 'Option 2', votes_count: 0, }, ], emojis: [], ...data, }); export const relationshipsFactory: FactoryFunction = ({ id, ...data } = {}) => ({ id: id ?? '1', following: false, followed_by: false, blocking: false, blocked_by: false, languages: null, muting: false, muting_notifications: false, muting_expires_at: null, note: '', requested_by: false, requested: false, domain_blocking: false, endorsed: false, notifying: false, showing_reblogs: true, ...data, }); export function unicodeEmojiFactory( data: Partial = {}, ): UnicodeEmojiData { return { hexcode: 'test', label: 'Test', unicode: '🧪', shortcodes: ['test_emoji'], tokens: ['emoji', 'test'], group: 1, order: 1, ...data, }; } export function customEmojiFactory( data: Partial = {}, ): CustomEmojiData { return { shortcode: 'custom', static_url: '/custom-emoji/logo.svg', url: '/custom-emoji/logo.svg', visible_in_picker: true, tokens: ['custom'], ...data, }; } interface AnnualReportState { state: 'available'; report: AnnualReport; } interface AnnualReportFactoryOptions { account_id?: string; status_id?: string; archetype?: AnnualReport['data']['archetype']; year?: number; top_hashtag?: AnnualReport['data']['top_hashtags'][0]; without_posts?: boolean; } export function annualReportFactory({ account_id = '1', status_id = '1', archetype = 'lurker', year, top_hashtag, without_posts = false, }: AnnualReportFactoryOptions = {}): AnnualReportState { return { state: 'available', report: { schema_version: 2, share_url: '#', account_id, year: year ?? 2025, data: { archetype, time_series: [ { month: 1, statuses: 0, followers: 0, following: 0, }, { month: 2, statuses: 0, followers: 0, following: 0, }, { month: 3, statuses: 0, followers: 0, following: 0, }, { month: 4, statuses: 0, followers: 0, following: 0, }, { month: 5, statuses: without_posts ? 0 : 1, followers: 1, following: 3, }, { month: 6, statuses: without_posts ? 0 : 7, followers: 1, following: 0, }, { month: 7, statuses: without_posts ? 0 : 2, followers: 0, following: 0, }, { month: 8, statuses: without_posts ? 0 : 2, followers: 0, following: 0, }, { month: 9, statuses: without_posts ? 0 : 11, followers: 0, following: 1, }, { month: 10, statuses: without_posts ? 0 : 12, followers: 0, following: 1, }, { month: 11, statuses: without_posts ? 0 : 6, followers: 0, following: 1, }, { month: 12, statuses: without_posts ? 0 : 4, followers: 0, following: 0, }, ], top_hashtags: top_hashtag ? [top_hashtag] : [], top_statuses: { by_reblogs: status_id, by_replies: status_id, by_favourites: status_id, }, }, }, }; }