Keep the profile payload smaller on first paint and fetch collections through a dedicated API when the collections tab is needed.
100 lines
3.3 KiB
React
100 lines
3.3 KiB
React
import React from 'react'
|
|
import { renderToString } from 'react-dom/server'
|
|
import { hydrateRoot } from 'react-dom/client'
|
|
import { act } from '@testing-library/react'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import TabCollections from './TabCollections'
|
|
|
|
const card = (id, overrides = {}) => ({
|
|
id,
|
|
title: `Collection ${id}`,
|
|
slug: `collection-${id}`,
|
|
type: 'personal',
|
|
mode: 'manual',
|
|
visibility: 'public',
|
|
is_featured: id === 1,
|
|
artworks_count: id === 1 ? 12 : 0,
|
|
likes_count: id === 1 ? 4 : 0,
|
|
followers_count: 0,
|
|
collaborators_count: 1,
|
|
updated_at: '2026-08-30T09:00:00.000000Z',
|
|
featured_at: id === 1 ? '2026-08-29T09:00:00.000000Z' : null,
|
|
description_excerpt: id === 1 ? 'A populated card.' : '',
|
|
smart_summary: null,
|
|
owner: { name: 'Fixture Owner', username: 'fixture', profile_url: '/@fixture/collections' },
|
|
cover_image: id === 1 ? 'https://cdn.example.test/cover.webp' : null,
|
|
cover_image_maturity: { should_blur: false, is_mature_effective: false },
|
|
saved: false,
|
|
save_url: null,
|
|
unsave_url: null,
|
|
login_url: '/login',
|
|
url: `/@fixture/collections/collection-${id}`,
|
|
...overrides,
|
|
})
|
|
|
|
const props = {
|
|
collections: [card(1), card(2)],
|
|
isOwner: false,
|
|
createUrl: null,
|
|
reorderUrl: null,
|
|
featuredUrl: '/collections/featured',
|
|
featureLimit: 3,
|
|
}
|
|
|
|
afterEach(() => {
|
|
document.body.innerHTML = ''
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
describe('TabCollections SSR hydration', () => {
|
|
it('hydrates populated collection cards without markup drift or errors', async () => {
|
|
const container = document.createElement('div')
|
|
const serverMarkup = renderToString(<TabCollections {...props} />)
|
|
container.innerHTML = serverMarkup
|
|
const parsedServerMarkup = container.innerHTML
|
|
document.body.appendChild(container)
|
|
const recoverableErrors = []
|
|
|
|
const root = hydrateRoot(container, <TabCollections {...props} />, {
|
|
onRecoverableError: (error) => recoverableErrors.push(error),
|
|
})
|
|
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
})
|
|
|
|
expect(container.innerHTML).toBe(parsedServerMarkup)
|
|
expect(recoverableErrors).toHaveLength(0)
|
|
expect(container.textContent).toContain('Collection 1')
|
|
expect(container.textContent).toContain('12 artworks')
|
|
|
|
await act(async () => root.unmount())
|
|
})
|
|
|
|
it('hydrates loaded-empty state without a lazy request', async () => {
|
|
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
|
const emptyProps = { ...props, collections: [] }
|
|
const container = document.createElement('div')
|
|
const serverMarkup = renderToString(<TabCollections {...emptyProps} />)
|
|
container.innerHTML = serverMarkup
|
|
const parsedServerMarkup = container.innerHTML
|
|
document.body.appendChild(container)
|
|
const recoverableErrors = []
|
|
|
|
const root = hydrateRoot(container, <TabCollections {...emptyProps} />, {
|
|
onRecoverableError: (error) => recoverableErrors.push(error),
|
|
})
|
|
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
})
|
|
|
|
expect(container.innerHTML).toBe(parsedServerMarkup)
|
|
expect(recoverableErrors).toHaveLength(0)
|
|
expect(container.textContent).toContain('No public collections yet')
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
|
|
await act(async () => root.unmount())
|
|
})
|
|
})
|