Skip to content

Commit 308d21c

Browse files
Merge pull request #102 from CSSLab/dev
Cleanup, tests, bug fixes, and optimizations
2 parents 9dce69f + 0e28859 commit 308d21c

35 files changed

Lines changed: 7825 additions & 1782 deletions

.github/workflows/ci.yml

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,33 @@ jobs:
99
lint:
1010
runs-on: ubuntu-latest
1111
steps:
12-
- uses: actions/checkout@v2
13-
- uses: actions/setup-node@v2
14-
- run: npm i
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-node@v4
14+
with:
15+
node-version: '20'
16+
cache: 'npm'
17+
- run: npm ci
1518
- run: npm run lint
1619

20+
test:
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: actions/setup-node@v4
25+
with:
26+
node-version: '20'
27+
cache: 'npm'
28+
- run: npm ci
29+
- run: npm test
30+
1731
build:
1832
runs-on: ubuntu-latest
19-
strategy:
20-
matrix:
21-
node: ['18']
22-
name: Node ${{ matrix.node }} Build
33+
needs: [lint, test]
2334
steps:
24-
- uses: actions/checkout@v2
25-
- uses: actions/setup-node@v2
35+
- uses: actions/checkout@v4
36+
- uses: actions/setup-node@v4
2637
with:
27-
node-version: ${{ matrix.node }}
28-
- run: npm i
38+
node-version: '20'
39+
cache: 'npm'
40+
- run: npm ci
2941
- run: npm run build
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 }: React.ComponentProps<'span'>) => (
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+
})

0 commit comments

Comments
 (0)