mirror of
https://git.shipoclu.com/moon/plspace.git
synced 2026-08-13 02:42:30 +00:00
public profile editing
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ApiClient } from './client'
|
||||
import { postStatus, updateProfileFields, votePoll } from './endpoints'
|
||||
import { postStatus, updateProfileFields, updatePublicProfile, votePoll } from './endpoints'
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
@@ -39,6 +39,56 @@ describe('updateProfileFields', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('updatePublicProfile', () => {
|
||||
it('sends public text, images, fields and Pleroma identity data as multipart form data', async () => {
|
||||
const avatar = new File(['avatar'], 'avatar.png', { type: 'image/png' })
|
||||
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
|
||||
const form = init?.body as FormData
|
||||
expect(init?.method).toBe('PATCH')
|
||||
expect(form.get('display_name')).toBe('Alice Example')
|
||||
expect(form.get('note')).toBe('My public bio')
|
||||
expect(form.get('avatar')).toBe(avatar)
|
||||
expect(form.get('header')).toBe('')
|
||||
expect(form.get('actor_type')).toBe('Group')
|
||||
expect(form.get('birthday')).toBe('2000-01-02')
|
||||
expect(form.get('show_birthday')).toBe('true')
|
||||
expect(form.get('fields_attributes[0][name]')).toBe('Website')
|
||||
expect(form.get('fields_attributes[0][value]')).toBe('https://example.test')
|
||||
return new Response(JSON.stringify({ id: 'account-1', fields: [] }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
await updatePublicProfile(new ApiClient('example.test', 'token'), {
|
||||
displayName: 'Alice Example',
|
||||
note: 'My public bio',
|
||||
avatar,
|
||||
header: '',
|
||||
actorType: 'Group',
|
||||
birthday: '2000-01-02',
|
||||
showBirthday: true,
|
||||
fields: [{ name: 'Website', value: 'https://example.test' }],
|
||||
})
|
||||
})
|
||||
|
||||
it('sends an empty sentinel row when all public fields are cleared', async () => {
|
||||
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
|
||||
const form = init?.body as FormData
|
||||
expect(form.get('fields_attributes[0][name]')).toBe('')
|
||||
expect(form.get('fields_attributes[0][value]')).toBe('')
|
||||
return new Response(JSON.stringify({ id: 'account-1', fields: [] }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
await updatePublicProfile(new ApiClient('example.test', 'token'), { fields: [] })
|
||||
})
|
||||
})
|
||||
|
||||
describe('poll endpoints', () => {
|
||||
it('submits selected poll option indexes as JSON', async () => {
|
||||
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
|
||||
|
||||
+58
-10
@@ -86,6 +86,63 @@ export function verifyCredentials(api: ApiClient): Promise<CredentialAccount> {
|
||||
return api.get<CredentialAccount>('/api/v1/accounts/verify_credentials')
|
||||
}
|
||||
|
||||
export interface PublicProfileUpdate {
|
||||
displayName?: string
|
||||
note?: string
|
||||
fields?: Array<{ name: string; value: string }>
|
||||
avatar?: File | '' | undefined
|
||||
header?: File | '' | undefined
|
||||
background?: File | '' | undefined
|
||||
avatarDescription?: string
|
||||
headerDescription?: string
|
||||
bot?: boolean
|
||||
actorType?: 'Person' | 'Service' | 'Group'
|
||||
birthday?: string
|
||||
showBirthday?: boolean
|
||||
}
|
||||
|
||||
/** Update only public-facing account profile data through the shared API. */
|
||||
export async function updatePublicProfile(
|
||||
api: ApiClient,
|
||||
update: PublicProfileUpdate,
|
||||
): Promise<CredentialAccount> {
|
||||
const form = new FormData()
|
||||
if (update.displayName !== undefined) form.set('display_name', update.displayName)
|
||||
if (update.note !== undefined) form.set('note', update.note)
|
||||
if (update.avatar !== undefined) form.set('avatar', update.avatar)
|
||||
if (update.header !== undefined) form.set('header', update.header)
|
||||
if (update.background !== undefined) form.set('pleroma_background_image', update.background)
|
||||
if (update.avatarDescription !== undefined) {
|
||||
form.set('avatar_description', update.avatarDescription)
|
||||
}
|
||||
if (update.headerDescription !== undefined) {
|
||||
form.set('header_description', update.headerDescription)
|
||||
}
|
||||
if (update.bot !== undefined) form.set('bot', String(update.bot))
|
||||
if (update.actorType !== undefined) form.set('actor_type', update.actorType)
|
||||
if (update.birthday !== undefined) form.set('birthday', update.birthday)
|
||||
if (update.showBirthday !== undefined) form.set('show_birthday', String(update.showBirthday))
|
||||
// An omitted collection means "leave fields unchanged". To explicitly clear
|
||||
// every field, submit one empty row; Pleroma and Mastodon both discard it
|
||||
// while still recognizing that fields_attributes was present.
|
||||
const submittedFields =
|
||||
update.fields === undefined
|
||||
? undefined
|
||||
: update.fields.length > 0
|
||||
? update.fields
|
||||
: [{ name: '', value: '' }]
|
||||
submittedFields?.forEach((field, index) => {
|
||||
form.append(`fields_attributes[${index}][name]`, field.name)
|
||||
form.append(`fields_attributes[${index}][value]`, field.value)
|
||||
})
|
||||
|
||||
const { data } = await api.raw<CredentialAccount>('/api/v1/accounts/update_credentials', {
|
||||
method: 'PATCH',
|
||||
form,
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the signed-in account's profile fields while leaving every other
|
||||
* credential untouched. Mastodon and Pleroma both accept this indexed
|
||||
@@ -95,16 +152,7 @@ export async function updateProfileFields(
|
||||
api: ApiClient,
|
||||
fields: Array<{ name: string; value: string }>,
|
||||
): Promise<CredentialAccount> {
|
||||
const form = new FormData()
|
||||
fields.forEach((field, index) => {
|
||||
form.append(`fields_attributes[${index}][name]`, field.name)
|
||||
form.append(`fields_attributes[${index}][value]`, field.value)
|
||||
})
|
||||
const { data } = await api.raw<CredentialAccount>('/api/v1/accounts/update_credentials', {
|
||||
method: 'PATCH',
|
||||
form,
|
||||
})
|
||||
return data
|
||||
return updatePublicProfile(api, { fields })
|
||||
}
|
||||
|
||||
export function fetchAccount(api: ApiClient, id: string): Promise<Account> {
|
||||
|
||||
@@ -47,8 +47,10 @@ export interface Account {
|
||||
uri?: string
|
||||
avatar: string
|
||||
avatar_static: string
|
||||
avatar_description?: string | null
|
||||
header: string
|
||||
header_static: string
|
||||
header_description?: string | null
|
||||
locked: boolean
|
||||
bot?: boolean
|
||||
group?: boolean
|
||||
@@ -84,6 +86,9 @@ export interface Account {
|
||||
relationship?: Relationship
|
||||
/** Some deployments expose the user's own profile CSS here. */
|
||||
background_color?: string | null
|
||||
birthday?: string | null
|
||||
avatar_description?: string | null
|
||||
header_description?: string | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +100,11 @@ export interface CredentialAccount extends Account {
|
||||
sensitive: boolean
|
||||
language: string | null
|
||||
follow_requests_count?: number
|
||||
pleroma?: {
|
||||
actor_type?: 'Person' | 'Service' | 'Group'
|
||||
show_birthday?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user