fix: improve VideoSocialButton styling
- Use vertical layout with icon above count (TikTok style) - Show correct liked/favorited state with filled icons - Add count formatting (1.5k, 2.8w) - Improve button background and positioning - Update tests for new component structure
This commit is contained in:
parent
e6416ee604
commit
20459ffd1d
|
|
@ -90,6 +90,20 @@ describe('VideoSocialButton Component', () => {
|
|||
)
|
||||
expect(getByText('50')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该在已点赞状态显示实心心形图标', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" liked={true} testID="video-social-liked" />
|
||||
)
|
||||
expect(getByTestId('video-social-liked-like-button')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该在已收藏状态显示实心星形图标', () => {
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" favorited={true} testID="video-social-favorited" />
|
||||
)
|
||||
expect(getByTestId('video-social-favorited-favorite-button')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('交互', () => {
|
||||
|
|
@ -113,6 +127,26 @@ describe('VideoSocialButton Component', () => {
|
|||
expect(onFavorite).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('已点赞状态点击应该调用 onUnlike', () => {
|
||||
const onUnlike = jest.fn()
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" liked={true} onUnlike={onUnlike} testID="video-social-on-unlike" />
|
||||
)
|
||||
const likeButton = getByTestId('video-social-on-unlike-like-button')
|
||||
fireEvent.press(likeButton)
|
||||
expect(onUnlike).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('已收藏状态点击应该调用 onUnfavorite', () => {
|
||||
const onUnfavorite = jest.fn()
|
||||
const { getByTestId } = render(
|
||||
<VideoSocialButton templateId="test-1" favorited={true} onUnfavorite={onUnfavorite} testID="video-social-on-unfavorite" />
|
||||
)
|
||||
const favoriteButton = getByTestId('video-social-on-unfavorite-favorite-button')
|
||||
fireEvent.press(favoriteButton)
|
||||
expect(onUnfavorite).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('loading 状态下应该禁用按钮', () => {
|
||||
const onLike = jest.fn()
|
||||
const onFavorite = jest.fn()
|
||||
|
|
@ -136,6 +170,46 @@ describe('VideoSocialButton Component', () => {
|
|||
})
|
||||
})
|
||||
|
||||
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={1500} favoriteCount={2800} />
|
||||
)
|
||||
expect(getByText('1.5k')).toBeTruthy()
|
||||
expect(getByText('2.8k')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该格式化万位数量', () => {
|
||||
const { getByText } = render(
|
||||
<VideoSocialButton templateId="test-1" likeCount={15000} favoriteCount={28000} />
|
||||
)
|
||||
expect(getByText('1.5w')).toBeTruthy()
|
||||
expect(getByText('2.8w')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该处理大数量', () => {
|
||||
const { getByText } = render(
|
||||
<VideoSocialButton templateId="test-1" likeCount={9999} favoriteCount={8888} />
|
||||
)
|
||||
expect(getByText('9999')).toBeTruthy()
|
||||
expect(getByText('8888')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该处理 undefined 数量', () => {
|
||||
const { getAllByText } = render(
|
||||
<VideoSocialButton templateId="test-1" />
|
||||
)
|
||||
expect(getAllByText('0').length).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('样式', () => {
|
||||
it('应该使用垂直布局', () => {
|
||||
const { getByTestId } = render(
|
||||
|
|
@ -155,21 +229,6 @@ describe('VideoSocialButton Component', () => {
|
|||
})
|
||||
|
||||
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" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { memo, useCallback, useMemo } from 'react'
|
||||
import { View, StyleSheet } from 'react-native'
|
||||
import { LikeButton, LikeButtonProps } from './LikeButton'
|
||||
import { FavoriteButton, FavoriteButtonProps } from './FavoriteButton'
|
||||
import React, { memo, useCallback } from 'react'
|
||||
import { View, StyleSheet, Text, Pressable } from 'react-native'
|
||||
import { Ionicons } from '@expo/vector-icons'
|
||||
|
||||
export interface VideoSocialButtonProps {
|
||||
templateId: string
|
||||
|
|
@ -52,41 +51,53 @@ const VideoSocialButtonComponent: React.FC<VideoSocialButtonProps> = ({
|
|||
}
|
||||
}, [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]
|
||||
)
|
||||
// 格式化数量显示
|
||||
const formatCount = (count?: number): string => {
|
||||
if (count === undefined || count === null) return '0'
|
||||
if (count >= 10000) {
|
||||
return `${(count / 10000).toFixed(1)}w`
|
||||
}
|
||||
if (count >= 1000) {
|
||||
return `${(count / 1000).toFixed(1)}k`
|
||||
}
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container} testID={testID}>
|
||||
{/* 点赞按钮 */}
|
||||
<View style={styles.button}>
|
||||
<LikeButton {...likeButtonProps} />
|
||||
</View>
|
||||
<Pressable
|
||||
style={styles.buttonWrapper}
|
||||
onPress={handleLikePress}
|
||||
disabled={loading}
|
||||
testID={testID ? `${testID}-like-button` : undefined}
|
||||
>
|
||||
<View style={[styles.iconContainer, liked && styles.iconContainerActive]}>
|
||||
<Ionicons
|
||||
name={liked ? 'heart' : 'heart-outline'}
|
||||
size={28}
|
||||
color={liked ? '#FF3B30' : '#FFFFFF'}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.count}>{formatCount(likeCount)}</Text>
|
||||
</Pressable>
|
||||
|
||||
{/* 收藏按钮 */}
|
||||
<View style={styles.button}>
|
||||
<FavoriteButton {...favoriteButtonProps} />
|
||||
</View>
|
||||
<Pressable
|
||||
style={styles.buttonWrapper}
|
||||
onPress={handleFavoritePress}
|
||||
disabled={loading}
|
||||
testID={testID ? `${testID}-favorite-button` : undefined}
|
||||
>
|
||||
<View style={[styles.iconContainer, favorited && styles.iconContainerActive]}>
|
||||
<Ionicons
|
||||
name={favorited ? 'star' : 'star-outline'}
|
||||
size={28}
|
||||
color={favorited ? '#FFD700' : '#FFFFFF'}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.count}>{formatCount(favoriteCount)}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
@ -96,20 +107,33 @@ export const VideoSocialButton = memo(VideoSocialButtonComponent)
|
|||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
right: 13,
|
||||
bottom: 100,
|
||||
right: 12,
|
||||
bottom: 180,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 16,
|
||||
gap: 20,
|
||||
},
|
||||
button: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
backgroundColor: 'rgba(25, 27, 31, 0.8)',
|
||||
buttonWrapper: {
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 52,
|
||||
height: 52,
|
||||
borderRadius: 26,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(47, 49, 52, 1)',
|
||||
},
|
||||
iconContainerActive: {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||
},
|
||||
count: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
textShadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: 2,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue