127 lines
4.8 KiB
React
127 lines
4.8 KiB
React
import React from 'react'
|
|
import { act, render, screen } from '@testing-library/react'
|
|
import { hydrateRoot } from 'react-dom/client'
|
|
import { renderToString } from 'react-dom/server'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import GroupProfileSummary from './GroupProfileSummary'
|
|
|
|
const backendFixture = {
|
|
group: {
|
|
id: 1,
|
|
name: 'Test Group',
|
|
slug: 'test-group',
|
|
headline: 'A public collaboration',
|
|
avatar_url: null,
|
|
profile_url: '/groups/test-group',
|
|
},
|
|
joined_at: '2026-08-01T12:00:00.000000Z',
|
|
role: 'member',
|
|
counts: {
|
|
credited_artworks: 12,
|
|
releases: 3,
|
|
projects: 2,
|
|
review_actions: 7,
|
|
},
|
|
trusted_indicator: true,
|
|
summary: 'Contributes across the group releases.',
|
|
role_labels: ['Coder', 'Designer'],
|
|
recent_release_titles: ['Release A', 'Release B'],
|
|
}
|
|
|
|
afterEach(() => {
|
|
document.body.innerHTML = ''
|
|
})
|
|
|
|
describe('GroupProfileSummary backend contract alignment', () => {
|
|
it('renders the current backend-shaped contribution fixture', () => {
|
|
render(<GroupProfileSummary contributions={[backendFixture]} />)
|
|
|
|
expect(screen.getByText('Test Group')).toBeTruthy()
|
|
expect(screen.getByText('member')).toBeTruthy()
|
|
expect(screen.getByText('Contributes across the group releases.')).toBeTruthy()
|
|
expect(screen.getByText('Trusted contributor')).toBeTruthy()
|
|
expect(screen.getByText('Coder')).toBeTruthy()
|
|
expect(screen.getByText('Designer')).toBeTruthy()
|
|
expect(screen.getByText('Release A, Release B')).toBeTruthy()
|
|
expect(screen.getByText('12 artworks')).toBeTruthy()
|
|
expect(screen.getByText('3 releases')).toBeTruthy()
|
|
expect(screen.getByText('2 projects')).toBeTruthy()
|
|
expect(screen.getByText('7 reviews')).toBeTruthy()
|
|
expect(screen.getByRole('link', { name: /Test Group/ }).getAttribute('href')).toBe('/groups/test-group')
|
|
})
|
|
|
|
it('does not render the trusted badge when trusted_indicator is false', () => {
|
|
render(<GroupProfileSummary contributions={[{ ...backendFixture, trusted_indicator: false }]} />)
|
|
|
|
expect(screen.queryByText('Trusted contributor')).toBeNull()
|
|
})
|
|
|
|
it('renders zero counts instead of hiding falsy values', () => {
|
|
render(<GroupProfileSummary contributions={[{
|
|
...backendFixture,
|
|
trusted_indicator: false,
|
|
counts: { credited_artworks: 0, releases: 0, projects: 0, review_actions: 0 },
|
|
role_labels: [],
|
|
recent_release_titles: [],
|
|
}]} />)
|
|
|
|
expect(screen.getByText('0 artworks')).toBeTruthy()
|
|
expect(screen.getByText('0 releases')).toBeTruthy()
|
|
expect(screen.getByText('0 projects')).toBeTruthy()
|
|
expect(screen.getByText('0 reviews')).toBeTruthy()
|
|
expect(screen.queryByText('Trusted contributor')).toBeNull()
|
|
expect(screen.queryByText('Recent releases')).toBeNull()
|
|
})
|
|
|
|
it('keeps the empty state unchanged', () => {
|
|
const { container } = render(<GroupProfileSummary contributions={[]} />)
|
|
|
|
expect(container.innerHTML).toBe('')
|
|
})
|
|
|
|
it('handles missing optional fields without throwing', () => {
|
|
render(<GroupProfileSummary contributions={[{ group: { id: 2, name: 'Minimal Group', slug: 'minimal' } }]} />)
|
|
|
|
expect(screen.getByText('Minimal Group')).toBeTruthy()
|
|
expect(screen.getByText('0 artworks')).toBeTruthy()
|
|
expect(screen.getByText('0 releases')).toBeTruthy()
|
|
expect(screen.getByText('0 projects')).toBeTruthy()
|
|
expect(screen.getByText('0 reviews')).toBeTruthy()
|
|
})
|
|
|
|
it('supports the previous frontend-shaped aliases as a compatibility fallback', () => {
|
|
render(<GroupProfileSummary contributions={[{
|
|
group: { id: 3, name: 'Legacy Group', slug: 'legacy', profile_url: '/groups/legacy' },
|
|
role: { label: 'editor' },
|
|
trusted: true,
|
|
counts: { artworks: 4, releases: 1, projects: 0 },
|
|
}]} />)
|
|
|
|
expect(screen.getByText('editor')).toBeTruthy()
|
|
expect(screen.getByText('Trusted contributor')).toBeTruthy()
|
|
expect(screen.getByText('4 artworks')).toBeTruthy()
|
|
})
|
|
|
|
it('hydrates populated backend data without recoverable errors or markup drift', async () => {
|
|
const container = document.createElement('div')
|
|
const serverMarkup = renderToString(<GroupProfileSummary contributions={[backendFixture]} />)
|
|
container.innerHTML = serverMarkup
|
|
document.body.appendChild(container)
|
|
const recoverableErrors = []
|
|
const root = hydrateRoot(container, <GroupProfileSummary contributions={[backendFixture]} />, {
|
|
onRecoverableError: (error) => recoverableErrors.push(error),
|
|
})
|
|
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
})
|
|
|
|
expect(container.innerHTML).toBe(serverMarkup)
|
|
expect(recoverableErrors).toHaveLength(0)
|
|
expect(container.textContent).toContain('12 artworks')
|
|
expect(container.textContent).toContain('Release A, Release B')
|
|
|
|
await act(async () => root.unmount())
|
|
})
|
|
})
|