feat: add VideoSocialButton component for video page
- Create vertical social button layout similar to TikTok/Kuaishou - Support like and favorite actions with proper state management - Include loading state handling - Add comprehensive tests covering all props and interactions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6fc87d59de
commit
ab79a9f200
|
|
@ -0,0 +1,193 @@
|
|||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react-native'
|
||||
import { VideoSocialButton } from './VideoSocialButton'
|
||||
|
||||
describe('VideoSocialButton Component', () => {
|
||||
describe('组件导出', () => {
|
||||
it('应该被定义', () => {
|
||||
expect(VideoSocialButton).toBeDefined()
|
||||
})
|
||||
|
||||
it('应该使用 React.memo 包装', () => {
|
||||
expect(typeof VideoSocialButton).toBe('object')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props 接口', () => {
|
||||
it('应该接受 templateId 属性', () => {
|
||||
const props = { templateId: 'test-template-1' }
|
||||
expect(props.templateId).toBe('test-template-1')
|
||||
})
|
||||
|
||||
it('应该接受 liked 属性', () => {
|
||||
const props = { liked: true }
|
||||
expect(props.liked).toBe(true)
|
||||
})
|
||||
|
||||
it('应该接受 favorited 属性', () => {
|
||||
const props = { favorited: true }
|
||||
expect(props.favorited).toBe(true)
|
||||
})
|
||||
|
||||
it('应该接受 likeCount 属性', () => {
|
||||
const props = { likeCount: 100 }
|
||||
expect(props.likeCount).toBe(100)
|
||||
})
|
||||
|
||||
it('应该接受 favoriteCount 属性', () => {
|
||||
const props = { favoriteCount: 50 }
|
||||
expect(props.favoriteCount).toBe(50)
|
||||
})
|
||||
|
||||
it('应该接受 loading 属性', () => {
|
||||
const props = { loading: true }
|
||||
expect(props.loading).toBe(true)
|
||||
})
|
||||
|
||||
it('应该接受 onLike 回调', () => {
|
||||
const onLike = jest.fn()
|
||||
expect(typeof onLike).toBe('function')
|
||||
})
|
||||
|
||||
it('应该接受 onFavorite 回调', () => {
|
||||
const onFavorite = jest.fn()
|
||||
expect(typeof onFavorite).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('渲染', () => {
|
||||
it('应该成功渲染', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" testID="video-social-button" />
|
||||
)
|
||||
expect(getByTestId('video-social-button')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该渲染点赞按钮', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" testID="video-social-like" />
|
||||
)
|
||||
expect(getByTestId('video-social-like-like-button')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该渲染收藏按钮', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" testID="video-social-favorite" />
|
||||
)
|
||||
expect(getByTestId('video-social-favorite-favorite-button')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该显示点赞数量', () => {
|
||||
const { getByText } = render(
|
||||
<VideoSocialButton templateId="test-1" likeCount={100} />
|
||||
)
|
||||
expect(getByText('100')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该显示收藏数量', () => {
|
||||
const { getByText } = render(
|
||||
<VideoSocialButton templateId="test-1" favoriteCount={50} />
|
||||
)
|
||||
expect(getByText('50')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('交互', () => {
|
||||
it('点击点赞按钮应该调用 onLike', () => {
|
||||
const onLike = jest.fn()
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" onLike={onLike} testID="video-social-on-like" />
|
||||
)
|
||||
const likeButton = getByTestId('video-social-on-like-like-button')
|
||||
fireEvent.press(likeButton)
|
||||
expect(onLike).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('点击收藏按钮应该调用 onFavorite', () => {
|
||||
const onFavorite = jest.fn()
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" onFavorite={onFavorite} testID="video-social-on-favorite" />
|
||||
)
|
||||
const favoriteButton = getByTestId('video-social-on-favorite-favorite-button')
|
||||
fireEvent.press(favoriteButton)
|
||||
expect(onFavorite).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('loading 状态下应该禁用按钮', () => {
|
||||
const onLike = jest.fn()
|
||||
const onFavorite = jest.fn()
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton
|
||||
templateId="test-1"
|
||||
loading={true}
|
||||
onLike={onLike}
|
||||
onFavorite={onFavorite}
|
||||
testID="video-social-loading"
|
||||
/>
|
||||
)
|
||||
const likeButton = getByTestId('video-social-loading-like-button')
|
||||
const favoriteButton = getByTestId('video-social-loading-favorite-button')
|
||||
|
||||
fireEvent.press(likeButton)
|
||||
fireEvent.press(favoriteButton)
|
||||
|
||||
expect(onLike).not.toHaveBeenCalled()
|
||||
expect(onFavorite).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('样式', () => {
|
||||
it('应该使用垂直布局', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" testID="video-social-vertical" />
|
||||
)
|
||||
const container = getByTestId('video-social-vertical')
|
||||
expect(container).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该使用圆形背景样式', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" testID="video-social-round" />
|
||||
)
|
||||
const container = getByTestId('video-social-round')
|
||||
expect(container).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('边界情况', () => {
|
||||
it('应该处理零数量', () => {
|
||||
const { getByText } = render(
|
||||
<VideoSocialButton templateId="test-1" likeCount={0} favoriteCount={0} />
|
||||
)
|
||||
expect(getByText('0')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该处理大数量', () => {
|
||||
const { getByText } = render(
|
||||
<VideoSocialButton templateId="test-1" likeCount={9999} favoriteCount={8888} />
|
||||
)
|
||||
expect(getByText('9999')).toBeTruthy()
|
||||
expect(getByText('8888')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该处理缺失的回调函数', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" testID="video-social-no-callback" />
|
||||
)
|
||||
const likeButton = getByTestId('video-social-no-callback-like-button')
|
||||
const favoriteButton = getByTestId('video-social-no-callback-favorite-button')
|
||||
|
||||
expect(() => {
|
||||
fireEvent.press(likeButton)
|
||||
fireEvent.press(favoriteButton)
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it('应该同时处理已点赞和已收藏状态', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" liked={true} favorited={true} testID="video-social-both" />
|
||||
)
|
||||
expect(getByTestId('video-social-both')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
import React, { memo, useCallback, useMemo } from 'react'
|
||||
import { View, StyleSheet } from 'react-native'
|
||||
import { LikeButton, LikeButtonProps } from './LikeButton'
|
||||
import { FavoriteButton, FavoriteButtonProps } from './FavoriteButton'
|
||||
|
||||
export interface VideoSocialButtonProps {
|
||||
templateId: string
|
||||
liked?: boolean
|
||||
favorited?: boolean
|
||||
likeCount?: number
|
||||
favoriteCount?: number
|
||||
loading?: boolean
|
||||
onLike?: () => void
|
||||
onUnlike?: () => void
|
||||
onFavorite?: () => void
|
||||
onUnfavorite?: () => void
|
||||
testID?: string
|
||||
}
|
||||
|
||||
const VideoSocialButtonComponent: React.FC<VideoSocialButtonProps> = ({
|
||||
templateId,
|
||||
liked = false,
|
||||
favorited = false,
|
||||
likeCount,
|
||||
favoriteCount,
|
||||
loading = false,
|
||||
onLike,
|
||||
onUnlike,
|
||||
onFavorite,
|
||||
onUnfavorite,
|
||||
testID,
|
||||
}) => {
|
||||
// 处理点赞按钮点击
|
||||
const handleLikePress = useCallback(() => {
|
||||
if (!loading) {
|
||||
if (liked) {
|
||||
onUnlike?.()
|
||||
} else {
|
||||
onLike?.()
|
||||
}
|
||||
}
|
||||
}, [loading, liked, onLike, onUnlike])
|
||||
|
||||
// 处理收藏按钮点击
|
||||
const handleFavoritePress = useCallback(() => {
|
||||
if (!loading) {
|
||||
if (favorited) {
|
||||
onUnfavorite?.()
|
||||
} else {
|
||||
onFavorite?.()
|
||||
}
|
||||
}
|
||||
}, [loading, favorited, onFavorite, onUnfavorite])
|
||||
|
||||
// LikeButton 的 props
|
||||
const likeButtonProps: LikeButtonProps = useMemo(
|
||||
() => ({
|
||||
liked,
|
||||
loading,
|
||||
count: likeCount,
|
||||
onPress: handleLikePress,
|
||||
testID: testID ? `${testID}-like-button` : undefined,
|
||||
}),
|
||||
[liked, loading, likeCount, handleLikePress, testID]
|
||||
)
|
||||
|
||||
// FavoriteButton 的 props
|
||||
const favoriteButtonProps: FavoriteButtonProps = useMemo(
|
||||
() => ({
|
||||
favorited,
|
||||
loading,
|
||||
count: favoriteCount,
|
||||
onPress: handleFavoritePress,
|
||||
testID: testID ? `${testID}-favorite-button` : undefined,
|
||||
}),
|
||||
[favorited, loading, favoriteCount, handleFavoritePress, testID]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.container} testID={testID}>
|
||||
{/* 点赞按钮 */}
|
||||
<View style={styles.button}>
|
||||
<LikeButton {...likeButtonProps} />
|
||||
</View>
|
||||
|
||||
{/* 收藏按钮 */}
|
||||
<View style={styles.button}>
|
||||
<FavoriteButton {...favoriteButtonProps} />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export const VideoSocialButton = memo(VideoSocialButtonComponent)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
right: 13,
|
||||
bottom: 100,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 16,
|
||||
},
|
||||
button: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
backgroundColor: 'rgba(25, 27, 31, 0.8)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(47, 49, 52, 1)',
|
||||
},
|
||||
})
|
||||
Loading…
Reference in New Issue