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()
|
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('交互', () => {
|
describe('交互', () => {
|
||||||
|
|
@ -113,6 +127,26 @@ describe('VideoSocialButton Component', () => {
|
||||||
expect(onFavorite).toHaveBeenCalledTimes(1)
|
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 状态下应该禁用按钮', () => {
|
it('loading 状态下应该禁用按钮', () => {
|
||||||
const onLike = jest.fn()
|
const onLike = jest.fn()
|
||||||
const onFavorite = 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('样式', () => {
|
describe('样式', () => {
|
||||||
it('应该使用垂直布局', () => {
|
it('应该使用垂直布局', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
|
|
@ -155,21 +229,6 @@ describe('VideoSocialButton Component', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('边界情况', () => {
|
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('应该处理缺失的回调函数', () => {
|
it('应该处理缺失的回调函数', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<VideoSocialButton templateId="test-1" testID="video-social-no-callback" />
|
<VideoSocialButton templateId="test-1" testID="video-social-no-callback" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { memo, useCallback, useMemo } from 'react'
|
import React, { memo, useCallback } from 'react'
|
||||||
import { View, StyleSheet } from 'react-native'
|
import { View, StyleSheet, Text, Pressable } from 'react-native'
|
||||||
import { LikeButton, LikeButtonProps } from './LikeButton'
|
import { Ionicons } from '@expo/vector-icons'
|
||||||
import { FavoriteButton, FavoriteButtonProps } from './FavoriteButton'
|
|
||||||
|
|
||||||
export interface VideoSocialButtonProps {
|
export interface VideoSocialButtonProps {
|
||||||
templateId: string
|
templateId: string
|
||||||
|
|
@ -52,41 +51,53 @@ const VideoSocialButtonComponent: React.FC<VideoSocialButtonProps> = ({
|
||||||
}
|
}
|
||||||
}, [loading, favorited, onFavorite, onUnfavorite])
|
}, [loading, favorited, onFavorite, onUnfavorite])
|
||||||
|
|
||||||
// LikeButton 的 props
|
// 格式化数量显示
|
||||||
const likeButtonProps: LikeButtonProps = useMemo(
|
const formatCount = (count?: number): string => {
|
||||||
() => ({
|
if (count === undefined || count === null) return '0'
|
||||||
liked,
|
if (count >= 10000) {
|
||||||
loading,
|
return `${(count / 10000).toFixed(1)}w`
|
||||||
count: likeCount,
|
}
|
||||||
onPress: handleLikePress,
|
if (count >= 1000) {
|
||||||
testID: testID ? `${testID}-like-button` : undefined,
|
return `${(count / 1000).toFixed(1)}k`
|
||||||
}),
|
}
|
||||||
[liked, loading, likeCount, handleLikePress, testID]
|
return count.toString()
|
||||||
)
|
}
|
||||||
|
|
||||||
// FavoriteButton 的 props
|
|
||||||
const favoriteButtonProps: FavoriteButtonProps = useMemo(
|
|
||||||
() => ({
|
|
||||||
favorited,
|
|
||||||
loading,
|
|
||||||
count: favoriteCount,
|
|
||||||
onPress: handleFavoritePress,
|
|
||||||
testID: testID ? `${testID}-favorite-button` : undefined,
|
|
||||||
}),
|
|
||||||
[favorited, loading, favoriteCount, handleFavoritePress, testID]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container} testID={testID}>
|
<View style={styles.container} testID={testID}>
|
||||||
{/* 点赞按钮 */}
|
{/* 点赞按钮 */}
|
||||||
<View style={styles.button}>
|
<Pressable
|
||||||
<LikeButton {...likeButtonProps} />
|
style={styles.buttonWrapper}
|
||||||
</View>
|
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}>
|
<Pressable
|
||||||
<FavoriteButton {...favoriteButtonProps} />
|
style={styles.buttonWrapper}
|
||||||
</View>
|
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>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -96,20 +107,33 @@ export const VideoSocialButton = memo(VideoSocialButtonComponent)
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 13,
|
right: 12,
|
||||||
bottom: 100,
|
bottom: 180,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 16,
|
gap: 20,
|
||||||
},
|
},
|
||||||
button: {
|
buttonWrapper: {
|
||||||
width: 48,
|
alignItems: 'center',
|
||||||
height: 48,
|
gap: 4,
|
||||||
borderRadius: 24,
|
},
|
||||||
backgroundColor: 'rgba(25, 27, 31, 0.8)',
|
iconContainer: {
|
||||||
|
width: 52,
|
||||||
|
height: 52,
|
||||||
|
borderRadius: 26,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: '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