mirror of
https://git.shipoclu.com/moon/plspace.git
synced 2026-08-13 02:42:30 +00:00
237 lines
7.5 KiB
TypeScript
237 lines
7.5 KiB
TypeScript
import { act, render, waitFor } from '@testing-library/svelte'
|
||
import { describe, expect, it, vi } from 'vitest'
|
||
import { APP_SERVICES } from '$lib/app-services'
|
||
import type { Account } from '$lib/api/types'
|
||
import { TIMELINE_REFRESH, TimelineRefreshController } from '$lib/timeline-refresh'
|
||
import { account, deferred, session, status, testServices, theme } from '$test/fixtures'
|
||
import Profile from './Profile.svelte'
|
||
|
||
describe('Profile', () => {
|
||
it('renders account custom emoji in the public profile heading', async () => {
|
||
const profileAccount = account({
|
||
display_name: ':smugkura: Kura :disconnecting: :kura_explode:',
|
||
emojis: [
|
||
{
|
||
shortcode: 'smugkura',
|
||
url: 'https://cdn.example/smugkura.png',
|
||
static_url: 'https://cdn.example/smugkura.png',
|
||
visible_in_picker: true,
|
||
},
|
||
{
|
||
shortcode: 'disconnecting',
|
||
url: 'https://cdn.example/disconnecting.png',
|
||
static_url: 'https://cdn.example/disconnecting.png',
|
||
visible_in_picker: true,
|
||
},
|
||
{
|
||
shortcode: 'kura_explode',
|
||
url: 'https://cdn.example/kura_explode.png',
|
||
static_url: 'https://cdn.example/kura_explode.png',
|
||
visible_in_picker: true,
|
||
},
|
||
],
|
||
})
|
||
const services = testServices({
|
||
session: session(),
|
||
theme: theme(),
|
||
endpoints: {
|
||
lookupAccount: vi.fn().mockResolvedValue(profileAccount),
|
||
fetchAccountStatuses: vi.fn().mockResolvedValue({ items: [], links: {} }),
|
||
},
|
||
})
|
||
const view = render(Profile, {
|
||
props: { acct: 'alice' },
|
||
context: new Map([[APP_SERVICES, services]]),
|
||
})
|
||
|
||
const headingEmoji = (await view.findAllByAltText(':smugkura:')).find((node) =>
|
||
node.closest('h1.profile-name'),
|
||
)
|
||
expect(headingEmoji).toBeDefined()
|
||
expect(view.getAllByAltText(':disconnecting:').length).toBeGreaterThan(0)
|
||
expect(view.getAllByAltText(':kura_explode:').length).toBeGreaterThan(0)
|
||
})
|
||
|
||
it('does not apply profile CSS after it has unmounted', async () => {
|
||
const lookup = deferred<Account>()
|
||
const applyProfileCss = vi.fn()
|
||
const services = testServices({
|
||
session: session(),
|
||
theme: theme({ applyProfileCss }),
|
||
endpoints: { lookupAccount: vi.fn(() => lookup.promise) },
|
||
})
|
||
const view = render(Profile, {
|
||
props: { acct: 'alice' },
|
||
context: new Map([[APP_SERVICES, services]]),
|
||
})
|
||
|
||
view.unmount()
|
||
await act(() => lookup.resolve(account({ fields: [{ name: 'css', value: '.x { color: red }' }] })))
|
||
|
||
expect(applyProfileCss).not.toHaveBeenCalled()
|
||
})
|
||
|
||
it('shows only original top-level notes in Latest Blog Entries', async () => {
|
||
const original = status({
|
||
id: 'original',
|
||
content: '<p>Original note</p>',
|
||
})
|
||
const boostedNote = status({
|
||
id: 'boost-wrapper',
|
||
content: '',
|
||
reblog: status({ id: 'boosted', content: '<p>Someone else’s note</p>' }),
|
||
})
|
||
const reply = status({
|
||
id: 'reply',
|
||
content: '<p>A reply</p>',
|
||
in_reply_to_id: 'parent',
|
||
})
|
||
const fetchAccountStatuses = vi.fn().mockResolvedValue({
|
||
items: [original, boostedNote, reply],
|
||
links: {},
|
||
})
|
||
const services = testServices({
|
||
session: session(),
|
||
theme: theme(),
|
||
endpoints: {
|
||
lookupAccount: vi.fn().mockResolvedValue(account()),
|
||
fetchAccountStatuses,
|
||
},
|
||
})
|
||
const view = render(Profile, {
|
||
props: { acct: 'alice', view: 'profile' },
|
||
context: new Map([[APP_SERVICES, services]]),
|
||
})
|
||
|
||
await waitFor(() => expect(fetchAccountStatuses).toHaveBeenCalledOnce())
|
||
expect(fetchAccountStatuses.mock.calls[0][3]).toEqual({
|
||
exclude_replies: true,
|
||
exclude_reblogs: true,
|
||
})
|
||
expect(await view.findByText('Original note')).toBeInTheDocument()
|
||
expect(view.queryByText('Someone else’s note')).not.toBeInTheDocument()
|
||
expect(view.queryByText('A reply')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('builds Pics from the account’s own image attachments', async () => {
|
||
const ownPicture = status({
|
||
id: 'picture-entry',
|
||
content: '<p>A day at the beach</p>',
|
||
media_attachments: [
|
||
{
|
||
id: 'picture',
|
||
type: 'image',
|
||
url: 'https://media.example/beach.jpg',
|
||
preview_url: 'https://media.example/beach-small.jpg',
|
||
description: 'Sunset over the beach',
|
||
},
|
||
],
|
||
})
|
||
const video = status({
|
||
id: 'video-entry',
|
||
content: '<p>A video that does not belong in Pics</p>',
|
||
media_attachments: [
|
||
{
|
||
id: 'video',
|
||
type: 'video',
|
||
url: 'https://media.example/movie.mp4',
|
||
preview_url: 'https://media.example/movie.jpg',
|
||
description: 'A movie',
|
||
},
|
||
],
|
||
})
|
||
const repostedPicture = status({
|
||
id: 'repost',
|
||
reblog: status({
|
||
id: 'someone-elses-picture',
|
||
content: '<p>Someone else’s picture</p>',
|
||
media_attachments: [
|
||
{
|
||
id: 'reposted-picture',
|
||
type: 'image',
|
||
url: 'https://media.example/reposted.jpg',
|
||
preview_url: null,
|
||
description: 'A reposted picture',
|
||
},
|
||
],
|
||
}),
|
||
})
|
||
const fetchAccountStatuses = vi.fn().mockResolvedValue({
|
||
items: [ownPicture, video, repostedPicture],
|
||
links: {},
|
||
})
|
||
const services = testServices({
|
||
session: session(),
|
||
theme: theme(),
|
||
endpoints: {
|
||
lookupAccount: vi.fn().mockResolvedValue(account()),
|
||
fetchAccountStatuses,
|
||
},
|
||
})
|
||
const view = render(Profile, {
|
||
props: { acct: 'alice', view: 'pics' },
|
||
context: new Map([[APP_SERVICES, services]]),
|
||
})
|
||
|
||
expect(await view.findByRole('img', { name: 'Sunset over the beach' })).toHaveAttribute(
|
||
'src',
|
||
'https://media.example/beach-small.jpg',
|
||
)
|
||
expect(fetchAccountStatuses.mock.calls[0][3]).toEqual({
|
||
only_media: true,
|
||
exclude_reblogs: true,
|
||
})
|
||
expect(view.getByText('Sunset over the beach')).toBeInTheDocument()
|
||
expect(view.queryByText('A movie')).not.toBeInTheDocument()
|
||
expect(view.queryByText('A reposted picture')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('refreshes only the entry feed without reloading the profile', async () => {
|
||
const lookupAccount = vi.fn().mockResolvedValue(account())
|
||
const fetchAccountStatuses = vi
|
||
.fn()
|
||
.mockResolvedValueOnce({
|
||
items: [
|
||
status({
|
||
id: 'timeline-note',
|
||
content: '<p>Timeline note</p>',
|
||
favourites_count: 0,
|
||
}),
|
||
],
|
||
links: {},
|
||
})
|
||
.mockResolvedValueOnce({
|
||
items: [
|
||
status({
|
||
id: 'timeline-note',
|
||
content: '<p>Timeline note</p>',
|
||
favourites_count: 5,
|
||
}),
|
||
],
|
||
links: {},
|
||
})
|
||
const services = testServices({
|
||
session: session(),
|
||
theme: theme(),
|
||
endpoints: { lookupAccount, fetchAccountStatuses },
|
||
})
|
||
const refresh = new TimelineRefreshController()
|
||
const context = new Map()
|
||
context.set(APP_SERVICES, services)
|
||
context.set(TIMELINE_REFRESH, refresh)
|
||
const view = render(Profile, {
|
||
props: { acct: 'alice', view: 'blog' },
|
||
context,
|
||
})
|
||
|
||
expect(await view.findByText('Timeline note')).toBeInTheDocument()
|
||
expect(refresh.refresh()).toBe(true)
|
||
|
||
await waitFor(() => expect(fetchAccountStatuses).toHaveBeenCalledTimes(2))
|
||
expect(lookupAccount).toHaveBeenCalledOnce()
|
||
await waitFor(() =>
|
||
expect(view.getByRole('button', { name: 'Kudos (5)' })).toBeInTheDocument(),
|
||
)
|
||
})
|
||
})
|