Make XP progress formatting hydration-safe
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import React from 'react'
|
||||
import { formatProfileNumber } from '../../lib/profileFormatters'
|
||||
|
||||
function formatXp(value) {
|
||||
return new Intl.NumberFormat().format(Number(value || 0))
|
||||
return formatProfileNumber(value)
|
||||
}
|
||||
|
||||
function clampPercent(value) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from 'react'
|
||||
import { hydrateRoot } from 'react-dom/client'
|
||||
import { act, render, screen } from '@testing-library/react'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import XPProgressBar from './XPProgressBar'
|
||||
import { formatProfileNumber } from '../../lib/profileFormatters'
|
||||
|
||||
const fixture = {
|
||||
xp: 3024,
|
||||
currentLevelXp: 2000,
|
||||
nextLevelXp: 5000,
|
||||
progressPercent: 34,
|
||||
}
|
||||
|
||||
describe('XPProgressBar locale-stable formatting', () => {
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
it.each([
|
||||
[0, '0'],
|
||||
[1, '1'],
|
||||
[999, '999'],
|
||||
[1000, '1,000'],
|
||||
[3024, '3,024'],
|
||||
[1000000, '1,000,000'],
|
||||
[9529106, '9,529,106'],
|
||||
])('formats %s as %s', (value, expected) => {
|
||||
expect(formatProfileNumber(value)).toBe(expected)
|
||||
})
|
||||
|
||||
it('renders the non-zero production-shaped XP value', () => {
|
||||
render(<XPProgressBar {...fixture} />)
|
||||
|
||||
expect(screen.getByText('3,024 XP')).toBeTruthy()
|
||||
expect(screen.getByText('2,000 / 5,000 XP')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('keeps XP zero formatting stable', () => {
|
||||
render(<XPProgressBar xp={0} currentLevelXp={0} nextLevelXp={100} progressPercent={0} />)
|
||||
|
||||
expect(screen.getByText('0 XP')).toBeTruthy()
|
||||
expect(screen.getByText('0 / 100 XP')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('hydrates the same SSR markup without recoverable errors', async () => {
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = renderToString(<XPProgressBar {...fixture} />)
|
||||
document.body.appendChild(container)
|
||||
const recoverableErrors = []
|
||||
const root = hydrateRoot(container, <XPProgressBar {...fixture} />, {
|
||||
onRecoverableError: (error) => recoverableErrors.push(error),
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(recoverableErrors).toHaveLength(0)
|
||||
expect(container.textContent).toContain('3,024 XP')
|
||||
expect(container.textContent).toContain('2,000 / 5,000 XP')
|
||||
await act(async () => root.unmount())
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user