diff --git a/src/routes/Profile.svelte b/src/routes/Profile.svelte index 08edc7f..7d0366e 100644 --- a/src/routes/Profile.svelte +++ b/src/routes/Profile.svelte @@ -106,11 +106,25 @@ theme.applyProfileCss(profileCssFromFields(found.fields)) entries = new Feed( - (cursor) => - endpoints.fetchAccountStatuses(session.api, found.id, cursor, { - // The profile page mirrors "Latest Blog Entries": top-level posts. - exclude_replies: currentView !== 'blog', - }), + async (cursor) => { + const preview = currentView !== 'blog' + const page = await endpoints.fetchAccountStatuses( + session.api, + found.id, + cursor, + preview ? { exclude_replies: true, exclude_reblogs: true } : {}, + ) + + // Pleroma-compatible forks do not all honour both exclusion flags. + // Defensively keep the profile preview to original, top-level Notes; + // the full Blog route still shows the complete account timeline. + return preview + ? { + ...page, + items: page.items.filter((item) => !item.reblog && !item.in_reply_to_id), + } + : page + }, currentView === 'blog' ? 20 : 10, ) void entries.reload() diff --git a/src/routes/Profile.test.ts b/src/routes/Profile.test.ts index 5fc650e..7293807 100644 --- a/src/routes/Profile.test.ts +++ b/src/routes/Profile.test.ts @@ -1,8 +1,8 @@ -import { act, render } from '@testing-library/svelte' +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 { account, deferred, session, testServices, theme } from '$test/fixtures' +import { account, deferred, session, status, testServices, theme } from '$test/fixtures' import Profile from './Profile.svelte' describe('Profile', () => { @@ -24,4 +24,46 @@ describe('Profile', () => { expect(applyProfileCss).not.toHaveBeenCalled() }) + + it('shows only original top-level notes in Latest Blog Entries', async () => { + const original = status({ + id: 'original', + content: '

Original note

', + }) + const boostedNote = status({ + id: 'boost-wrapper', + content: '', + reblog: status({ id: 'boosted', content: '

Someone else’s note

' }), + }) + const reply = status({ + id: 'reply', + content: '

A reply

', + 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() + }) })