Skip to content

Commit 0499886

Browse files
feat: add automated tests for some common components
1 parent 9dce69f commit 0499886

23 files changed

Lines changed: 9851 additions & 3439 deletions
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { render, screen } from '@testing-library/react'
2+
import { AnimatedNumber } from '../../src/components/Common/AnimatedNumber'
3+
4+
// Mock framer-motion to avoid complex animation testing
5+
let mockValue = 1000
6+
jest.mock('framer-motion', () => ({
7+
motion: {
8+
span: ({ children, className, ...props }: any) => (
9+
<span className={className} {...props}>
10+
{children}
11+
</span>
12+
),
13+
},
14+
useSpring: jest.fn((value) => {
15+
mockValue = value
16+
return {
17+
set: jest.fn((newValue) => {
18+
mockValue = newValue
19+
}),
20+
get: jest.fn(() => mockValue),
21+
}
22+
}),
23+
useTransform: jest.fn((_, transform) => {
24+
return transform(mockValue)
25+
}),
26+
}))
27+
28+
describe('AnimatedNumber Component', () => {
29+
beforeEach(() => {
30+
jest.clearAllMocks()
31+
})
32+
33+
it('should render with default formatting', () => {
34+
render(<AnimatedNumber value={1000} />)
35+
36+
// The component should render the formatted value
37+
expect(screen.getByText('1,000')).toBeInTheDocument()
38+
})
39+
40+
it('should apply custom className', () => {
41+
render(<AnimatedNumber value={1000} className="custom-class" />)
42+
43+
const element = screen.getByText('1,000')
44+
expect(element).toHaveClass('custom-class')
45+
})
46+
47+
it('should use custom formatValue function', () => {
48+
const customFormat = (value: number) => `$${value.toFixed(2)}`
49+
render(<AnimatedNumber value={1000} formatValue={customFormat} />)
50+
51+
expect(screen.getByText('$1000.00')).toBeInTheDocument()
52+
})
53+
54+
it('should handle zero value', () => {
55+
render(<AnimatedNumber value={0} />)
56+
57+
expect(screen.getByText('0')).toBeInTheDocument()
58+
})
59+
60+
it('should handle negative values', () => {
61+
render(<AnimatedNumber value={-500} />)
62+
63+
expect(screen.getByText('-500')).toBeInTheDocument()
64+
})
65+
66+
it('should handle decimal values with default rounding', () => {
67+
render(<AnimatedNumber value={1234.56} />)
68+
69+
expect(screen.getByText('1,235')).toBeInTheDocument()
70+
})
71+
72+
it('should handle large numbers', () => {
73+
render(<AnimatedNumber value={1000000} />)
74+
75+
expect(screen.getByText('1,000,000')).toBeInTheDocument()
76+
})
77+
78+
it('should use custom duration prop', () => {
79+
const { rerender } = render(<AnimatedNumber value={1000} duration={2.5} />)
80+
81+
// Test that component renders without error with custom duration
82+
expect(screen.getByText('1,000')).toBeInTheDocument()
83+
84+
// Rerender with different value to test duration effect
85+
rerender(<AnimatedNumber value={2000} duration={0.5} />)
86+
expect(screen.getByText('2,000')).toBeInTheDocument()
87+
})
88+
89+
it('should handle percentage formatting', () => {
90+
const percentFormat = (value: number) => `${(value * 100).toFixed(1)}%`
91+
render(<AnimatedNumber value={0.85} formatValue={percentFormat} />)
92+
93+
expect(screen.getByText('85.0%')).toBeInTheDocument()
94+
})
95+
96+
it('should handle currency formatting', () => {
97+
const currencyFormat = (value: number) =>
98+
new Intl.NumberFormat('en-US', {
99+
style: 'currency',
100+
currency: 'USD',
101+
}).format(value)
102+
103+
render(<AnimatedNumber value={1234.56} formatValue={currencyFormat} />)
104+
105+
expect(screen.getByText('$1,234.56')).toBeInTheDocument()
106+
})
107+
108+
it('should render as motion.span element', () => {
109+
render(<AnimatedNumber value={1000} />)
110+
111+
const element = screen.getByText('1,000')
112+
expect(element.tagName).toBe('SPAN')
113+
})
114+
})
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { render, screen } from '@testing-library/react'
2+
import { AuthenticatedWrapper } from '../../src/components/Common/AuthenticatedWrapper'
3+
import { AuthContext } from '../../src/contexts/AuthContext'
4+
import { User } from '../../src/types/auth'
5+
6+
const mockUser: User = {
7+
clientId: 'test-client-id',
8+
displayName: 'TestUser',
9+
lichessId: 'testuser',
10+
}
11+
12+
const AuthProvider = ({
13+
user,
14+
children,
15+
}: {
16+
user: User | null
17+
children: React.ReactNode
18+
}) => (
19+
<AuthContext.Provider
20+
value={{
21+
user,
22+
connectLichess: jest.fn(),
23+
logout: jest.fn(),
24+
}}
25+
>
26+
{children}
27+
</AuthContext.Provider>
28+
)
29+
30+
describe('AuthenticatedWrapper Component', () => {
31+
it('should render children when user is authenticated', () => {
32+
render(
33+
<AuthProvider user={mockUser}>
34+
<AuthenticatedWrapper>
35+
<div>Protected content</div>
36+
</AuthenticatedWrapper>
37+
</AuthProvider>,
38+
)
39+
40+
expect(screen.getByText('Protected content')).toBeInTheDocument()
41+
})
42+
43+
it('should not render children when user is not authenticated', () => {
44+
render(
45+
<AuthProvider user={null}>
46+
<AuthenticatedWrapper>
47+
<div>Protected content</div>
48+
</AuthenticatedWrapper>
49+
</AuthProvider>,
50+
)
51+
52+
expect(screen.queryByText('Protected content')).not.toBeInTheDocument()
53+
})
54+
55+
it('should handle multiple children when user is authenticated', () => {
56+
render(
57+
<AuthProvider user={mockUser}>
58+
<AuthenticatedWrapper>
59+
<div>First child</div>
60+
<div>Second child</div>
61+
<span>Third child</span>
62+
</AuthenticatedWrapper>
63+
</AuthProvider>,
64+
)
65+
66+
expect(screen.getByText('First child')).toBeInTheDocument()
67+
expect(screen.getByText('Second child')).toBeInTheDocument()
68+
expect(screen.getByText('Third child')).toBeInTheDocument()
69+
})
70+
71+
it('should not render multiple children when user is not authenticated', () => {
72+
render(
73+
<AuthProvider user={null}>
74+
<AuthenticatedWrapper>
75+
<div>First child</div>
76+
<div>Second child</div>
77+
<span>Third child</span>
78+
</AuthenticatedWrapper>
79+
</AuthProvider>,
80+
)
81+
82+
expect(screen.queryByText('First child')).not.toBeInTheDocument()
83+
expect(screen.queryByText('Second child')).not.toBeInTheDocument()
84+
expect(screen.queryByText('Third child')).not.toBeInTheDocument()
85+
})
86+
87+
it('should handle no children gracefully when user is authenticated', () => {
88+
render(
89+
<AuthProvider user={mockUser}>
90+
<AuthenticatedWrapper />
91+
</AuthProvider>,
92+
)
93+
94+
// Should not crash and should render empty fragment
95+
expect(screen.queryByText(/./)).not.toBeInTheDocument()
96+
})
97+
98+
it('should handle no children gracefully when user is not authenticated', () => {
99+
render(
100+
<AuthProvider user={null}>
101+
<AuthenticatedWrapper />
102+
</AuthProvider>,
103+
)
104+
105+
// Should not crash and should render empty fragment
106+
expect(screen.queryByText(/./)).not.toBeInTheDocument()
107+
})
108+
109+
it('should re-render when authentication state changes', () => {
110+
const { rerender } = render(
111+
<AuthProvider user={null}>
112+
<AuthenticatedWrapper>
113+
<div>Protected content</div>
114+
</AuthenticatedWrapper>
115+
</AuthProvider>,
116+
)
117+
118+
// Initially not authenticated
119+
expect(screen.queryByText('Protected content')).not.toBeInTheDocument()
120+
121+
// Re-render with authenticated user
122+
rerender(
123+
<AuthProvider user={mockUser}>
124+
<AuthenticatedWrapper>
125+
<div>Protected content</div>
126+
</AuthenticatedWrapper>
127+
</AuthProvider>,
128+
)
129+
130+
expect(screen.getByText('Protected content')).toBeInTheDocument()
131+
132+
// Re-render back to unauthenticated
133+
rerender(
134+
<AuthProvider user={null}>
135+
<AuthenticatedWrapper>
136+
<div>Protected content</div>
137+
</AuthenticatedWrapper>
138+
</AuthProvider>,
139+
)
140+
141+
expect(screen.queryByText('Protected content')).not.toBeInTheDocument()
142+
})
143+
})
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { render, screen } from '@testing-library/react'
2+
import { Compose } from '../../src/components/Common/Compose'
3+
import { ErrorBoundary } from '../../src/components/Common/ErrorBoundary'
4+
5+
// Mock ErrorBoundary to avoid chessground import issues
6+
jest.mock('../../src/components/Common/ErrorBoundary', () => ({
7+
ErrorBoundary: ({ children }: { children: React.ReactNode }) => (
8+
<div data-testid="error-boundary">{children}</div>
9+
),
10+
}))
11+
12+
// Mock providers for testing
13+
const MockProvider1 = ({ children }: { children: React.ReactNode }) => (
14+
<div data-testid="provider-1">{children}</div>
15+
)
16+
17+
const MockProvider2 = ({ children }: { children: React.ReactNode }) => (
18+
<div data-testid="provider-2">{children}</div>
19+
)
20+
21+
const MockProvider3 = ({ children }: { children: React.ReactNode }) => (
22+
<div data-testid="provider-3">{children}</div>
23+
)
24+
25+
describe('Compose Component', () => {
26+
it('should render children with single component', () => {
27+
render(
28+
<Compose components={[MockProvider1]}>
29+
<div data-testid="child">Test Child</div>
30+
</Compose>,
31+
)
32+
33+
expect(screen.getByTestId('provider-1')).toBeInTheDocument()
34+
expect(screen.getByTestId('child')).toBeInTheDocument()
35+
expect(screen.getByText('Test Child')).toBeInTheDocument()
36+
})
37+
38+
it('should nest multiple components correctly', () => {
39+
render(
40+
<Compose components={[MockProvider1, MockProvider2]}>
41+
<div data-testid="child">Nested Child</div>
42+
</Compose>,
43+
)
44+
45+
expect(screen.getByTestId('provider-1')).toBeInTheDocument()
46+
expect(screen.getByTestId('provider-2')).toBeInTheDocument()
47+
expect(screen.getByTestId('child')).toBeInTheDocument()
48+
49+
// Verify nesting order
50+
const provider1 = screen.getByTestId('provider-1')
51+
const provider2 = screen.getByTestId('provider-2')
52+
expect(provider1).toContainElement(provider2)
53+
})
54+
55+
it('should handle three levels of nesting', () => {
56+
render(
57+
<Compose components={[MockProvider1, MockProvider2, MockProvider3]}>
58+
<div data-testid="child">Deep Nested Child</div>
59+
</Compose>,
60+
)
61+
62+
expect(screen.getByTestId('provider-1')).toBeInTheDocument()
63+
expect(screen.getByTestId('provider-2')).toBeInTheDocument()
64+
expect(screen.getByTestId('provider-3')).toBeInTheDocument()
65+
expect(screen.getByTestId('child')).toBeInTheDocument()
66+
67+
// Verify deep nesting
68+
const provider1 = screen.getByTestId('provider-1')
69+
const provider2 = screen.getByTestId('provider-2')
70+
const provider3 = screen.getByTestId('provider-3')
71+
expect(provider1).toContainElement(provider2)
72+
expect(provider2).toContainElement(provider3)
73+
})
74+
75+
it('should work with ErrorBoundary component', () => {
76+
render(
77+
<Compose components={[ErrorBoundary, MockProvider1]}>
78+
<div data-testid="child">Error Wrapped Child</div>
79+
</Compose>,
80+
)
81+
82+
expect(screen.getByTestId('error-boundary')).toBeInTheDocument()
83+
expect(screen.getByTestId('provider-1')).toBeInTheDocument()
84+
expect(screen.getByTestId('child')).toBeInTheDocument()
85+
})
86+
87+
it('should handle empty components array', () => {
88+
render(
89+
<Compose components={[]}>
90+
<div data-testid="child">Unwrapped Child</div>
91+
</Compose>,
92+
)
93+
94+
expect(screen.getByTestId('child')).toBeInTheDocument()
95+
expect(screen.getByText('Unwrapped Child')).toBeInTheDocument()
96+
})
97+
98+
it('should render multiple children', () => {
99+
render(
100+
<Compose components={[MockProvider1]}>
101+
<div data-testid="child-1">First Child</div>
102+
<div data-testid="child-2">Second Child</div>
103+
</Compose>,
104+
)
105+
106+
expect(screen.getByTestId('provider-1')).toBeInTheDocument()
107+
expect(screen.getByTestId('child-1')).toBeInTheDocument()
108+
expect(screen.getByTestId('child-2')).toBeInTheDocument()
109+
expect(screen.getByText('First Child')).toBeInTheDocument()
110+
expect(screen.getByText('Second Child')).toBeInTheDocument()
111+
})
112+
113+
it('should preserve React node types', () => {
114+
render(
115+
<Compose components={[MockProvider1]}>
116+
<span>Text node</span>
117+
<button>Button node</button>
118+
<input placeholder="Input node" />
119+
</Compose>,
120+
)
121+
122+
expect(screen.getByText('Text node')).toBeInTheDocument()
123+
expect(
124+
screen.getByRole('button', { name: 'Button node' }),
125+
).toBeInTheDocument()
126+
expect(screen.getByPlaceholderText('Input node')).toBeInTheDocument()
127+
})
128+
})

0 commit comments

Comments
 (0)