import React from 'react'
import { render, fireEvent } from '@testing-library/react-native'
import Text from './Text'
describe('Text Component', () => {
describe('Basic Rendering', () => {
it('should render basic text with default dark theme color', () => {
const { getByText } = render(Hello World)
const textElement = getByText('Hello World')
expect(textElement).toBeTruthy()
expect(textElement.props.style).toEqual(
expect.objectContaining({
color: '#F5F5F5',
})
)
})
it('should render children correctly', () => {
const { getByText } = render(Test Content)
expect(getByText('Test Content')).toBeTruthy()
})
it('should render nested text elements', () => {
const { getByText } = render(
Parent Child
)
// In React Native, nested Text components are flattened into a single text node
expect(getByText('Parent Child')).toBeTruthy()
expect(getByText('Child')).toBeTruthy()
})
it('should have correct displayName', () => {
expect(Text.displayName).toBe('Text')
})
})
describe('Style Merging', () => {
it('should merge custom object style with default style', () => {
const customStyle = { fontSize: 16, fontWeight: '600' as const }
const { getByText } = render(Styled Text)
const textElement = getByText('Styled Text')
expect(textElement.props.style).toEqual(
expect.objectContaining({
color: '#F5F5F5',
fontSize: 16,
fontWeight: '600',
})
)
})
it('should merge custom array style with default style', () => {
const styleArray = [{ fontSize: 14 }, { fontWeight: '500' as const }]
const { getByText } = render(Array Style)
const textElement = getByText('Array Style')
// Array style should be merged as [defaultStyle, ...styleArray]
expect(textElement.props.style).toContainEqual({ color: '#F5F5F5' })
expect(textElement.props.style).toContainEqual({ fontSize: 14 })
expect(textElement.props.style).toContainEqual({ fontWeight: '500' })
})
it('should allow overriding default color', () => {
const { getByText } = render(
Red Text
)
const textElement = getByText('Red Text')
expect(textElement.props.style.color).toBe('#FF0000')
})
it('should pass className through correctly', () => {
const { getByText } = render(Class Text)
const textElement = getByText('Class Text')
expect(textElement.props.className).toBe('text-primary')
})
})
describe('onClick Handler', () => {
it('should create TouchableOpacity wrapper when onClick is provided', () => {
const mockOnClick = jest.fn()
const { getByText } = render(Clickable Text)
const textElement = getByText('Clickable Text')
// Parent should be TouchableOpacity (or TouchableWithoutFeedback depending on implementation)
expect(textElement.parent).toBeTruthy()
})
it('should call onClick handler when pressed', () => {
const mockOnClick = jest.fn()
const { getByText } = render(Click Me)
const textElement = getByText('Click Me')
fireEvent.press(textElement)
expect(mockOnClick).toHaveBeenCalledTimes(1)
})
it('should pass touchProps to TouchableOpacity', () => {
const mockOnClick = jest.fn()
const touchProps = {
testID: 'touchable-test',
activeOpacity: 0.5,
}
const { getByTestId } = render(
Touchable Text
)
expect(getByTestId('touchable-test')).toBeTruthy()
})
it('should not interfere with normal text rendering when onClick is not provided', () => {
const { getByText } = render(Normal Text)
const textElement = getByText('Normal Text')
expect(textElement).toBeTruthy()
// Should be plain Text element, not wrapped
expect(textElement.parent.type).not.toBe('TouchableOpacity')
})
})
describe('Animated Text', () => {
it('should render Animated.Text when animated prop is true', () => {
const { getByText } = render(Animated Text)
const textElement = getByText('Animated Text')
expect(textElement).toBeTruthy()
// Animated.Text should still render the content
})
it('should pass style to Animated.Text component', () => {
const customStyle = { fontSize: 20 }
const { getByText } = render(
Animated Styled
)
const textElement = getByText('Animated Styled')
expect(textElement.props.style).toEqual(
expect.objectContaining({
color: '#F5F5F5',
fontSize: 20,
})
)
})
})
describe('Props Forwarding', () => {
it('should forward standard RN Text props', () => {
const { getByText } = render(
Long text that should be truncated
)
const textElement = getByText(/Long text/)
expect(textElement.props.numberOfLines).toBe(2)
expect(textElement.props.ellipsizeMode).toBe('tail')
})
it('should forward accessibility props', () => {
const { getByText } = render(
Content
)
const textElement = getByText('Content')
expect(textElement.props.accessibilityLabel).toBe('Label text')
expect(textElement.props.accessible).toBe(true)
})
it('should forward testID prop', () => {
const { getByTestId } = render(Test Content)
expect(getByTestId('text-test-id')).toBeTruthy()
})
it('should forward onPress through onClick', () => {
const mockOnPress = jest.fn()
const { getByText } = render(Press Me)
const textElement = getByText('Press Me')
fireEvent.press(textElement)
expect(mockOnPress).toHaveBeenCalled()
})
})
describe('Edge Cases', () => {
it('should handle empty string children', () => {
const { getByText } = render()
// Empty text should still render
const textElement = getByText('')
expect(textElement).toBeTruthy()
})
it('should handle null children gracefully', () => {
// null should not cause crash, component renders but with no text content
const result = render({null})
expect(result).toBeTruthy()
})
it('should handle style as undefined', () => {
const { getByText } = render(No Style)
const textElement = getByText('No Style')
expect(textElement.props.style).toEqual({ color: '#F5F5F5' })
})
it('should handle className as undefined', () => {
const { getByText } = render(No Class)
const textElement = getByText('No Class')
expect(textElement.props.className).toBe('')
})
it('should handle complex style objects', () => {
const complexStyle = {
fontSize: 16,
fontWeight: 'bold' as const,
lineHeight: 24,
letterSpacing: 0.5,
textTransform: 'uppercase' as const,
}
const { getByText } = render(Complex Style)
const textElement = getByText('Complex Style')
expect(textElement.props.style).toEqual(
expect.objectContaining({
color: '#F5F5F5',
fontSize: 16,
fontWeight: 'bold',
lineHeight: 24,
letterSpacing: 0.5,
textTransform: 'uppercase',
})
)
})
it('should prioritize onClick over animated when both are provided', () => {
const mockOnClick = jest.fn()
const { getByText } = render(
Both Props
)
const textElement = getByText('Both Props')
// animated prop takes precedence in the conditional rendering
// onClick is ignored when animated is true based on the implementation
})
})
describe('Integration with Other Components', () => {
it('should work within a View component', () => {
const { getByText } = render(
Nested Text
)
// In React Native, nested Text components are flattened into a single text node
expect(getByText('Nested Text')).toBeTruthy()
expect(getByText('Nested')).toBeTruthy()
})
it('should handle multiple onClick handlers correctly', () => {
const onClick1 = jest.fn()
const onClick2 = jest.fn()
const { getByText } = render(
<>
First
Second
>
)
fireEvent.press(getByText('First'))
fireEvent.press(getByText('Second'))
expect(onClick1).toHaveBeenCalledTimes(1)
expect(onClick2).toHaveBeenCalledTimes(1)
})
})
})