@umituz/react-native-design-system 2.3.14 → 2.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +19 -2
- package/src/index.ts +91 -0
- package/src/layouts/ScreenLayout/ScreenLayout.example.tsx +2 -2
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +1 -1
- package/src/molecules/animation/core/AnimationCore.ts +29 -0
- package/src/molecules/animation/domain/entities/Animation.ts +81 -0
- package/src/molecules/animation/domain/entities/Fireworks.ts +44 -0
- package/src/molecules/animation/domain/entities/Theme.ts +76 -0
- package/src/molecules/animation/index.ts +146 -0
- package/src/molecules/animation/infrastructure/services/AnimationConfigService.ts +35 -0
- package/src/molecules/animation/infrastructure/services/SpringAnimationConfigService.ts +67 -0
- package/src/molecules/animation/infrastructure/services/TimingAnimationConfigService.ts +57 -0
- package/src/molecules/animation/infrastructure/services/__tests__/SpringAnimationConfigService.test.ts +114 -0
- package/src/molecules/animation/infrastructure/services/__tests__/TimingAnimationConfigService.test.ts +105 -0
- package/src/molecules/animation/presentation/components/Fireworks.tsx +126 -0
- package/src/molecules/animation/presentation/components/__tests__/Fireworks.test.tsx +189 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useAnimation.integration.test.ts +216 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useFireworks.test.ts +242 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useGesture.test.ts +111 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useSpringAnimation.test.ts +131 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useTimingAnimation.test.ts +175 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useTransformAnimation.test.ts +137 -0
- package/src/molecules/animation/presentation/hooks/useAnimation.ts +77 -0
- package/src/molecules/animation/presentation/hooks/useFireworks.ts +141 -0
- package/src/molecules/animation/presentation/hooks/useGesture.ts +61 -0
- package/src/molecules/animation/presentation/hooks/useGestureCreators.ts +163 -0
- package/src/molecules/animation/presentation/hooks/useGestureState.ts +53 -0
- package/src/molecules/animation/presentation/hooks/useIconAnimations.ts +119 -0
- package/src/molecules/animation/presentation/hooks/useModalAnimations.ts +124 -0
- package/src/molecules/animation/presentation/hooks/useReanimatedReady.ts +60 -0
- package/src/molecules/animation/presentation/hooks/useSpringAnimation.ts +69 -0
- package/src/molecules/animation/presentation/hooks/useTimingAnimation.ts +111 -0
- package/src/molecules/animation/presentation/hooks/useTransformAnimation.ts +57 -0
- package/src/molecules/animation/presentation/providers/AnimationThemeProvider.tsx +62 -0
- package/src/molecules/animation/presentation/providers/__tests__/AnimationThemeProvider.test.tsx +165 -0
- package/src/molecules/animation/types/global.d.ts +97 -0
- package/src/molecules/celebration/domain/entities/CelebrationConfig.ts +17 -0
- package/src/molecules/celebration/domain/entities/FireworksConfig.ts +32 -0
- package/src/molecules/celebration/index.ts +93 -0
- package/src/molecules/celebration/infrastructure/services/FireworksConfigService.ts +49 -0
- package/src/molecules/celebration/presentation/components/CelebrationFireworksOverlay.tsx +33 -0
- package/src/molecules/celebration/presentation/components/CelebrationModal.tsx +78 -0
- package/src/molecules/celebration/presentation/components/CelebrationModalContent.tsx +90 -0
- package/src/molecules/celebration/presentation/hooks/useCelebrationModalAnimation.ts +49 -0
- package/src/molecules/celebration/presentation/hooks/useCelebrationState.ts +45 -0
- package/src/molecules/celebration/presentation/styles/CelebrationModalStyles.ts +65 -0
- package/src/molecules/countdown/components/Countdown.tsx +128 -0
- package/src/molecules/countdown/components/CountdownHeader.tsx +84 -0
- package/src/molecules/countdown/components/TimeUnit.tsx +73 -0
- package/src/molecules/countdown/hooks/useCountdown.ts +107 -0
- package/src/molecules/countdown/index.ts +25 -0
- package/src/molecules/countdown/types/CountdownTypes.ts +31 -0
- package/src/molecules/countdown/utils/TimeCalculator.ts +46 -0
- package/src/molecules/emoji/domain/entities/Emoji.ts +129 -0
- package/src/molecules/emoji/index.ts +177 -0
- package/src/molecules/emoji/presentation/components/EmojiPicker.tsx +102 -0
- package/src/molecules/emoji/presentation/hooks/useEmojiPicker.ts +171 -0
- package/src/molecules/index.ts +21 -0
- package/src/molecules/long-press-menu/domain/entities/MenuAction.ts +37 -0
- package/src/molecules/long-press-menu/index.ts +16 -0
- package/src/molecules/navigation/StackNavigator.tsx +75 -0
- package/src/molecules/navigation/TabsNavigator.tsx +94 -0
- package/src/molecules/navigation/components/FabButton.tsx +45 -0
- package/src/molecules/navigation/components/TabLabel.tsx +47 -0
- package/src/molecules/navigation/createStackNavigator.ts +20 -0
- package/src/molecules/navigation/createTabNavigator.ts +20 -0
- package/src/molecules/navigation/hooks/useTabBarStyles.ts +54 -0
- package/src/molecules/navigation/index.ts +37 -0
- package/src/molecules/navigation/types.ts +118 -0
- package/src/molecules/navigation/utils/AppNavigation.ts +101 -0
- package/src/molecules/navigation/utils/IconRenderer.ts +50 -0
- package/src/molecules/navigation/utils/LabelProcessor.ts +70 -0
- package/src/molecules/navigation/utils/NavigationCleanup.ts +62 -0
- package/src/molecules/navigation/utils/NavigationTheme.ts +21 -0
- package/src/molecules/navigation/utils/NavigationValidator.ts +61 -0
- package/src/molecules/navigation/utils/ScreenFactory.ts +115 -0
- package/src/molecules/navigation/utils/__tests__/IconRenderer.getIconName.test.ts +109 -0
- package/src/molecules/navigation/utils/__tests__/IconRenderer.renderIcon.test.ts +116 -0
- package/src/molecules/navigation/utils/__tests__/LabelProcessor.processLabel.test.ts +116 -0
- package/src/molecules/navigation/utils/__tests__/LabelProcessor.processTitle.test.ts +59 -0
- package/src/molecules/navigation/utils/__tests__/NavigationCleanup.test.ts +271 -0
- package/src/molecules/navigation/utils/__tests__/NavigationValidator.test.ts +252 -0
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +194 -0
- package/src/molecules/swipe-actions/index.ts +6 -0
- package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +131 -0
- package/src/theme/hooks/useResponsiveDesignTokens.ts +1 -1
- package/src/utilities/clipboard/ClipboardUtils.ts +71 -0
- package/src/utilities/clipboard/index.ts +5 -0
- package/src/utilities/index.ts +6 -0
- package/src/utilities/sharing/domain/entities/Share.ts +210 -0
- package/src/utilities/sharing/index.ts +205 -0
- package/src/utilities/sharing/infrastructure/services/SharingService.ts +165 -0
- package/src/utilities/sharing/presentation/hooks/useSharing.ts +154 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAnimation Hook Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Integration tests for combined animation functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { renderHook, act } from '@testing-library/react';
|
|
8
|
+
import { useAnimation } from '../useAnimation';
|
|
9
|
+
|
|
10
|
+
// Mock react-native-reanimated
|
|
11
|
+
jest.mock('react-native-reanimated', () => ({
|
|
12
|
+
useSharedValue: jest.fn((initialValue) => ({
|
|
13
|
+
value: initialValue,
|
|
14
|
+
})),
|
|
15
|
+
useAnimatedStyle: jest.fn((styleFactory) => styleFactory()),
|
|
16
|
+
withTiming: jest.fn((toValue, config) => ({ toValue, config })),
|
|
17
|
+
withSpring: jest.fn((toValue, config) => ({ toValue, config })),
|
|
18
|
+
withSequence: jest.fn((...animations) => animations),
|
|
19
|
+
withRepeat: jest.fn((animation, count, reverse) => ({ animation, count, reverse })),
|
|
20
|
+
Easing: {
|
|
21
|
+
ease: jest.fn(),
|
|
22
|
+
out: jest.fn((easing) => easing),
|
|
23
|
+
bezier: jest.fn(() => jest.fn()),
|
|
24
|
+
linear: jest.fn(),
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock all sub-hooks
|
|
29
|
+
jest.mock('../useTimingAnimation', () => ({
|
|
30
|
+
useTimingAnimation: jest.fn(() => ({
|
|
31
|
+
fadeIn: jest.fn(),
|
|
32
|
+
fadeOut: jest.fn(),
|
|
33
|
+
slideInUp: jest.fn(),
|
|
34
|
+
slideInDown: jest.fn(),
|
|
35
|
+
slideInLeft: jest.fn(),
|
|
36
|
+
slideInRight: jest.fn(),
|
|
37
|
+
opacity: { value: 1 },
|
|
38
|
+
translateY: { value: 0 },
|
|
39
|
+
translateX: { value: 0 },
|
|
40
|
+
})),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
jest.mock('../useSpringAnimation', () => ({
|
|
44
|
+
useSpringAnimation: jest.fn(() => ({
|
|
45
|
+
scaleIn: jest.fn(),
|
|
46
|
+
scaleOut: jest.fn(),
|
|
47
|
+
bounce: jest.fn(),
|
|
48
|
+
scale: { value: 1 },
|
|
49
|
+
})),
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
jest.mock('../useTransformAnimation', () => ({
|
|
53
|
+
useTransformAnimation: jest.fn(() => ({
|
|
54
|
+
shake: jest.fn(),
|
|
55
|
+
pulse: jest.fn(),
|
|
56
|
+
spin: jest.fn(),
|
|
57
|
+
translateX: { value: 0 },
|
|
58
|
+
scale: { value: 1 },
|
|
59
|
+
rotate: { value: 0 },
|
|
60
|
+
})),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
describe('useAnimation Integration', () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
jest.clearAllMocks();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should integrate all animation hooks', () => {
|
|
69
|
+
const { useTimingAnimation } = require('../useTimingAnimation');
|
|
70
|
+
const { useSpringAnimation } = require('../useSpringAnimation');
|
|
71
|
+
const { useTransformAnimation } = require('../useTransformAnimation');
|
|
72
|
+
|
|
73
|
+
renderHook(() => useAnimation());
|
|
74
|
+
|
|
75
|
+
expect(useTimingAnimation).toHaveBeenCalled();
|
|
76
|
+
expect(useSpringAnimation).toHaveBeenCalled();
|
|
77
|
+
expect(useTransformAnimation).toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should provide all timing animations', () => {
|
|
81
|
+
const { result } = renderHook(() => useAnimation());
|
|
82
|
+
|
|
83
|
+
expect(typeof result.current.fadeIn).toBe('function');
|
|
84
|
+
expect(typeof result.current.fadeOut).toBe('function');
|
|
85
|
+
expect(typeof result.current.slideInUp).toBe('function');
|
|
86
|
+
expect(typeof result.current.slideInDown).toBe('function');
|
|
87
|
+
expect(typeof result.current.slideInLeft).toBe('function');
|
|
88
|
+
expect(typeof result.current.slideInRight).toBe('function');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should provide all spring animations', () => {
|
|
92
|
+
const { result } = renderHook(() => useAnimation());
|
|
93
|
+
|
|
94
|
+
expect(typeof result.current.scaleIn).toBe('function');
|
|
95
|
+
expect(typeof result.current.scaleOut).toBe('function');
|
|
96
|
+
expect(typeof result.current.bounce).toBe('function');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should provide all transform animations', () => {
|
|
100
|
+
const { result } = renderHook(() => useAnimation());
|
|
101
|
+
|
|
102
|
+
expect(typeof result.current.shake).toBe('function');
|
|
103
|
+
expect(typeof result.current.pulse).toBe('function');
|
|
104
|
+
expect(typeof result.current.spin).toBe('function');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should provide all shared values', () => {
|
|
108
|
+
const { result } = renderHook(() => useAnimation());
|
|
109
|
+
|
|
110
|
+
expect(result.current.opacity).toBeDefined();
|
|
111
|
+
expect(result.current.translateY).toBeDefined();
|
|
112
|
+
expect(result.current.translateX).toBeDefined();
|
|
113
|
+
expect(result.current.scale).toBeDefined();
|
|
114
|
+
expect(result.current.rotate).toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should provide animated style', () => {
|
|
118
|
+
const { result } = renderHook(() => useAnimation());
|
|
119
|
+
const { useAnimatedStyle } = require('react-native-reanimated');
|
|
120
|
+
|
|
121
|
+
expect(typeof result.current.animatedStyle).toBe('function');
|
|
122
|
+
expect(useAnimatedStyle).toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('animation combinations', () => {
|
|
126
|
+
it('should allow combining fade and scale animations', () => {
|
|
127
|
+
const { result } = renderHook(() => useAnimation());
|
|
128
|
+
const mockTiming = require('../useTimingAnimation').useTimingAnimation();
|
|
129
|
+
const mockSpring = require('../useSpringAnimation').useSpringAnimation();
|
|
130
|
+
|
|
131
|
+
act(() => {
|
|
132
|
+
result.current.fadeIn();
|
|
133
|
+
result.current.scaleIn();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(mockTiming.fadeIn).toHaveBeenCalled();
|
|
137
|
+
expect(mockSpring.scaleIn).toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should allow combining slide and rotate animations', () => {
|
|
141
|
+
const { result } = renderHook(() => useAnimation());
|
|
142
|
+
const mockTiming = require('../useTimingAnimation').useTimingAnimation();
|
|
143
|
+
const mockTransform = require('../useTransformAnimation').useTransformAnimation();
|
|
144
|
+
|
|
145
|
+
act(() => {
|
|
146
|
+
result.current.slideInUp();
|
|
147
|
+
result.current.spin();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(mockTiming.slideInUp).toHaveBeenCalled();
|
|
151
|
+
expect(mockTransform.spin).toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should allow complex animation sequences', () => {
|
|
155
|
+
const { result } = renderHook(() => useAnimation());
|
|
156
|
+
const mockTiming = require('../useTimingAnimation').useTimingAnimation();
|
|
157
|
+
const mockSpring = require('../useSpringAnimation').useSpringAnimation();
|
|
158
|
+
const mockTransform = require('../useTransformAnimation').useTransformAnimation();
|
|
159
|
+
|
|
160
|
+
act(() => {
|
|
161
|
+
result.current.fadeIn();
|
|
162
|
+
result.current.slideInUp();
|
|
163
|
+
result.current.scaleIn();
|
|
164
|
+
result.current.bounce();
|
|
165
|
+
result.current.shake();
|
|
166
|
+
result.current.pulse();
|
|
167
|
+
result.current.spin();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(mockTiming.fadeIn).toHaveBeenCalled();
|
|
171
|
+
expect(mockTiming.slideInUp).toHaveBeenCalled();
|
|
172
|
+
expect(mockSpring.scaleIn).toHaveBeenCalled();
|
|
173
|
+
expect(mockSpring.bounce).toHaveBeenCalled();
|
|
174
|
+
expect(mockTransform.shake).toHaveBeenCalled();
|
|
175
|
+
expect(mockTransform.pulse).toHaveBeenCalled();
|
|
176
|
+
expect(mockTransform.spin).toHaveBeenCalled();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('animated style composition', () => {
|
|
181
|
+
it('should compose styles from all animation types', () => {
|
|
182
|
+
const { useAnimatedStyle } = require('react-native-reanimated');
|
|
183
|
+
const mockStyle = {
|
|
184
|
+
opacity: 1,
|
|
185
|
+
transform: [
|
|
186
|
+
{ translateY: 0 },
|
|
187
|
+
{ translateX: 0 },
|
|
188
|
+
{ scale: 1 },
|
|
189
|
+
{ rotate: '0deg' },
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
useAnimatedStyle.mockReturnValue(mockStyle);
|
|
194
|
+
|
|
195
|
+
const { result } = renderHook(() => useAnimation());
|
|
196
|
+
|
|
197
|
+
expect(result.current.animatedStyle).toBe(mockStyle);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should prioritize transform values over timing/spring values', () => {
|
|
201
|
+
const { useAnimatedStyle } = require('react-native-reanimated');
|
|
202
|
+
const mockTiming = require('../useTimingAnimation').useTimingAnimation();
|
|
203
|
+
const mockTransform = require('../useTransformAnimation').useTransformAnimation();
|
|
204
|
+
|
|
205
|
+
// Set transform values to non-default
|
|
206
|
+
mockTransform.translateX.value = 50;
|
|
207
|
+
mockTransform.scale.value = 1.5;
|
|
208
|
+
|
|
209
|
+
renderHook(() => useAnimation());
|
|
210
|
+
|
|
211
|
+
expect(useAnimatedStyle).toHaveBeenCalledWith(
|
|
212
|
+
expect.any(Function)
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFireworks Hook Tests
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for fireworks particle system.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { renderHook, act } from '@testing-library/react';
|
|
8
|
+
import { useFireworks } from '../useFireworks';
|
|
9
|
+
import { FIREWORKS_CONSTANTS } from '../../../domain/entities/Animation';
|
|
10
|
+
|
|
11
|
+
// Mock requestAnimationFrame
|
|
12
|
+
global.requestAnimationFrame = jest.fn((cb) => {
|
|
13
|
+
setTimeout(cb, 16);
|
|
14
|
+
return 1;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
global.cancelAnimationFrame = jest.fn();
|
|
18
|
+
|
|
19
|
+
describe('useFireworks', () => {
|
|
20
|
+
const mockConfig = {
|
|
21
|
+
colors: ['#FF0000', '#00FF00', '#0000FF'],
|
|
22
|
+
particleCount: 10,
|
|
23
|
+
duration: 1000,
|
|
24
|
+
particleSize: 4,
|
|
25
|
+
spread: 50,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
jest.clearAllMocks();
|
|
30
|
+
jest.useFakeTimers();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
jest.useRealTimers();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should initialize with empty particles and inactive state', () => {
|
|
38
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
39
|
+
|
|
40
|
+
expect(result.current.particles).toEqual([]);
|
|
41
|
+
expect(result.current.isActive).toBe(false);
|
|
42
|
+
expect(typeof result.current.trigger).toBe('function');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should require colors array', () => {
|
|
46
|
+
const { result } = renderHook(() => useFireworks({ colors: [] }));
|
|
47
|
+
|
|
48
|
+
expect(result.current.particles).toEqual([]);
|
|
49
|
+
expect(result.current.trigger).toEqual(expect.any(Function));
|
|
50
|
+
expect(result.current.isActive).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('trigger', () => {
|
|
54
|
+
it('should create particles with correct count', () => {
|
|
55
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
56
|
+
|
|
57
|
+
act(() => {
|
|
58
|
+
result.current.trigger(100, 100);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(result.current.particles).toHaveLength(mockConfig.particleCount);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should create particles with correct properties', () => {
|
|
65
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
66
|
+
|
|
67
|
+
act(() => {
|
|
68
|
+
result.current.trigger(100, 100);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const particles = result.current.particles;
|
|
72
|
+
particles.forEach(particle => {
|
|
73
|
+
expect(particle).toHaveProperty('x');
|
|
74
|
+
expect(particle).toHaveProperty('y');
|
|
75
|
+
expect(particle).toHaveProperty('color');
|
|
76
|
+
expect(particle).toHaveProperty('size');
|
|
77
|
+
expect(particle).toHaveProperty('velocityX');
|
|
78
|
+
expect(particle).toHaveProperty('velocityY');
|
|
79
|
+
expect(particle).toHaveProperty('life');
|
|
80
|
+
expect(particle).toHaveProperty('decay');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should use provided colors', () => {
|
|
85
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
86
|
+
|
|
87
|
+
act(() => {
|
|
88
|
+
result.current.trigger(100, 100);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const particles = result.current.particles;
|
|
92
|
+
const particleColors = particles.map(p => p.color);
|
|
93
|
+
|
|
94
|
+
particleColors.forEach(color => {
|
|
95
|
+
expect(mockConfig.colors).toContain(color);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should set particles at trigger position', () => {
|
|
100
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
101
|
+
const triggerX = 150;
|
|
102
|
+
const triggerY = 200;
|
|
103
|
+
|
|
104
|
+
act(() => {
|
|
105
|
+
result.current.trigger(triggerX, triggerY);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const particles = result.current.particles;
|
|
109
|
+
particles.forEach(particle => {
|
|
110
|
+
expect(particle.x).toBe(triggerX);
|
|
111
|
+
expect(particle.y).toBe(triggerY);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should use default position when none provided', () => {
|
|
116
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
117
|
+
|
|
118
|
+
act(() => {
|
|
119
|
+
result.current.trigger();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const particles = result.current.particles;
|
|
123
|
+
particles.forEach(particle => {
|
|
124
|
+
expect(particle.x).toBe(0);
|
|
125
|
+
expect(particle.y).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should set active state to true', () => {
|
|
130
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
131
|
+
|
|
132
|
+
act(() => {
|
|
133
|
+
result.current.trigger(100, 100);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(result.current.isActive).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should start animation loop', () => {
|
|
140
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
141
|
+
|
|
142
|
+
act(() => {
|
|
143
|
+
result.current.trigger(100, 100);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(global.requestAnimationFrame).toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should auto-stop after duration', () => {
|
|
150
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
151
|
+
|
|
152
|
+
act(() => {
|
|
153
|
+
result.current.trigger(100, 100);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(result.current.isActive).toBe(true);
|
|
157
|
+
|
|
158
|
+
act(() => {
|
|
159
|
+
jest.advanceTimersByTime(mockConfig.duration);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(result.current.isActive).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('particle physics', () => {
|
|
167
|
+
it('should update particle positions over time', () => {
|
|
168
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
169
|
+
|
|
170
|
+
act(() => {
|
|
171
|
+
result.current.trigger(100, 100);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const initialParticles = [...result.current.particles];
|
|
175
|
+
|
|
176
|
+
act(() => {
|
|
177
|
+
jest.advanceTimersByTime(16); // One frame
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const updatedParticles = result.current.particles;
|
|
181
|
+
|
|
182
|
+
updatedParticles.forEach((particle, index) => {
|
|
183
|
+
const initial = initialParticles[index];
|
|
184
|
+
expect(particle.x).not.toBe(initial.x);
|
|
185
|
+
expect(particle.y).not.toBe(initial.y);
|
|
186
|
+
expect(particle.life).toBeLessThan(initial.life);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should apply gravity to particles', () => {
|
|
191
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
192
|
+
|
|
193
|
+
act(() => {
|
|
194
|
+
result.current.trigger(100, 100);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const initialParticles = [...result.current.particles];
|
|
198
|
+
|
|
199
|
+
act(() => {
|
|
200
|
+
jest.advanceTimersByTime(16);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const updatedParticles = result.current.particles;
|
|
204
|
+
|
|
205
|
+
updatedParticles.forEach((particle, index) => {
|
|
206
|
+
const initial = initialParticles[index];
|
|
207
|
+
const expectedY = initial.y + initial.velocityY * 0.1 + FIREWORKS_CONSTANTS.GRAVITY;
|
|
208
|
+
expect(particle.y).toBeCloseTo(expectedY, 1);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should remove dead particles', () => {
|
|
213
|
+
const { result } = renderHook(() => useFireworks(mockConfig));
|
|
214
|
+
|
|
215
|
+
act(() => {
|
|
216
|
+
result.current.trigger(100, 100);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Simulate particles dying
|
|
220
|
+
act(() => {
|
|
221
|
+
jest.advanceTimersByTime(2000); // Long enough for particles to die
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(result.current.particles).toHaveLength(0);
|
|
225
|
+
expect(result.current.isActive).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('cleanup', () => {
|
|
230
|
+
it('should cancel animation frame on unmount', () => {
|
|
231
|
+
const { result, unmount } = renderHook(() => useFireworks(mockConfig));
|
|
232
|
+
|
|
233
|
+
act(() => {
|
|
234
|
+
result.current.trigger(100, 100);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
unmount();
|
|
238
|
+
|
|
239
|
+
expect(global.cancelAnimationFrame).toHaveBeenCalled();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useGesture Hook Tests
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for gesture handling functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { renderHook } from '@testing-library/react';
|
|
8
|
+
import { useGesture } from '../useGesture';
|
|
9
|
+
|
|
10
|
+
// Mock react-native-gesture-handler
|
|
11
|
+
jest.mock('react-native-gesture-handler', () => ({
|
|
12
|
+
GestureDetector: jest.fn(({ children }) => children),
|
|
13
|
+
Gesture: {
|
|
14
|
+
Tap: jest.fn(() => ({
|
|
15
|
+
numberOfTaps: jest.fn().mockReturnThis(),
|
|
16
|
+
maxDuration: jest.fn().mockReturnThis(),
|
|
17
|
+
onStart: jest.fn().mockReturnThis(),
|
|
18
|
+
})),
|
|
19
|
+
Pan: jest.fn(() => ({
|
|
20
|
+
onStart: jest.fn().mockReturnThis(),
|
|
21
|
+
onUpdate: jest.fn().mockReturnThis(),
|
|
22
|
+
onEnd: jest.fn().mockReturnThis(),
|
|
23
|
+
})),
|
|
24
|
+
Pinch: jest.fn(() => ({
|
|
25
|
+
onStart: jest.fn().mockReturnThis(),
|
|
26
|
+
onUpdate: jest.fn().mockReturnThis(),
|
|
27
|
+
onEnd: jest.fn().mockReturnThis(),
|
|
28
|
+
})),
|
|
29
|
+
LongPress: jest.fn(() => ({
|
|
30
|
+
minDuration: jest.fn().mockReturnThis(),
|
|
31
|
+
onStart: jest.fn().mockReturnThis(),
|
|
32
|
+
})),
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Mock useGestureState
|
|
37
|
+
jest.mock('../useGestureState', () => ({
|
|
38
|
+
useGestureState: jest.fn(() => ({
|
|
39
|
+
translateX: { value: 0 },
|
|
40
|
+
translateY: { value: 0 },
|
|
41
|
+
scale: { value: 1 },
|
|
42
|
+
savedTranslateX: { value: 0 },
|
|
43
|
+
savedTranslateY: { value: 0 },
|
|
44
|
+
savedScale: { value: 1 },
|
|
45
|
+
reset: jest.fn(),
|
|
46
|
+
animatedStyle: {},
|
|
47
|
+
})),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// Mock useGestureCreators
|
|
51
|
+
jest.mock('../useGestureCreators', () => ({
|
|
52
|
+
useGestureCreators: jest.fn(() => ({
|
|
53
|
+
createTapGesture: jest.fn(),
|
|
54
|
+
createPanGesture: jest.fn(),
|
|
55
|
+
createPinchGesture: jest.fn(),
|
|
56
|
+
createLongPressGesture: jest.fn(),
|
|
57
|
+
})),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
describe('useGesture', () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
jest.clearAllMocks();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should provide gesture creators', () => {
|
|
66
|
+
const { result } = renderHook(() => useGesture());
|
|
67
|
+
|
|
68
|
+
expect(typeof result.current.createTapGesture).toBe('function');
|
|
69
|
+
expect(typeof result.current.createPanGesture).toBe('function');
|
|
70
|
+
expect(typeof result.current.createPinchGesture).toBe('function');
|
|
71
|
+
expect(typeof result.current.createLongPressGesture).toBe('function');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should provide shared values', () => {
|
|
75
|
+
const { result } = renderHook(() => useGesture());
|
|
76
|
+
|
|
77
|
+
expect(result.current.translateX).toBeDefined();
|
|
78
|
+
expect(result.current.translateY).toBeDefined();
|
|
79
|
+
expect(result.current.scale).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should provide utilities', () => {
|
|
83
|
+
const { result } = renderHook(() => useGesture());
|
|
84
|
+
|
|
85
|
+
expect(typeof result.current.reset).toBe('function');
|
|
86
|
+
expect(result.current.animatedStyle).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should export GestureDetector', () => {
|
|
90
|
+
const { result } = renderHook(() => useGesture());
|
|
91
|
+
const { GestureDetector } = require('react-native-gesture-handler');
|
|
92
|
+
|
|
93
|
+
expect(result.current.GestureDetector).toBe(GestureDetector);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('gesture creators integration', () => {
|
|
97
|
+
it('should integrate with useGestureCreators', () => {
|
|
98
|
+
const { useGestureCreators } = require('../useGestureCreators');
|
|
99
|
+
renderHook(() => useGesture());
|
|
100
|
+
|
|
101
|
+
expect(useGestureCreators).toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should integrate with useGestureState', () => {
|
|
105
|
+
const { useGestureState } = require('../useGestureState');
|
|
106
|
+
renderHook(() => useGesture());
|
|
107
|
+
|
|
108
|
+
expect(useGestureState).toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSpringAnimation Hook Tests
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for spring-based animations (scale, bounce).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { renderHook, act } from '@testing-library/react';
|
|
8
|
+
import { useSpringAnimation } from '../useSpringAnimation';
|
|
9
|
+
import { AnimationPreset, ANIMATION_CONSTANTS } from '../../../domain/entities/Animation';
|
|
10
|
+
|
|
11
|
+
// Mock react-native-reanimated
|
|
12
|
+
jest.mock('react-native-reanimated', () => ({
|
|
13
|
+
useSharedValue: jest.fn((initialValue) => ({
|
|
14
|
+
value: initialValue,
|
|
15
|
+
})),
|
|
16
|
+
useAnimatedStyle: jest.fn((styleFactory) => styleFactory()),
|
|
17
|
+
withSpring: jest.fn((toValue, config) => ({ toValue, config })),
|
|
18
|
+
withSequence: jest.fn((...animations) => animations),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Mock SpringAnimationConfigService
|
|
22
|
+
jest.mock('../../../infrastructure/services/SpringAnimationConfigService', () => ({
|
|
23
|
+
SpringAnimationConfigService: {
|
|
24
|
+
getSpringConfig: jest.fn((preset) => ({
|
|
25
|
+
damping: ANIMATION_CONSTANTS.SPRING.DAMPING,
|
|
26
|
+
stiffness: ANIMATION_CONSTANTS.SPRING.STIFFNESS,
|
|
27
|
+
})),
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
describe('useSpringAnimation', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should initialize with default values', () => {
|
|
37
|
+
const { result } = renderHook(() => useSpringAnimation());
|
|
38
|
+
|
|
39
|
+
expect(result.current.scale.value).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should provide animation functions', () => {
|
|
43
|
+
const { result } = renderHook(() => useSpringAnimation());
|
|
44
|
+
|
|
45
|
+
expect(typeof result.current.scaleIn).toBe('function');
|
|
46
|
+
expect(typeof result.current.scaleOut).toBe('function');
|
|
47
|
+
expect(typeof result.current.bounce).toBe('function');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('scaleIn', () => {
|
|
51
|
+
it('should animate scale from 0 to 1 with default config', () => {
|
|
52
|
+
const { result } = renderHook(() => useSpringAnimation());
|
|
53
|
+
const { withSpring } = require('react-native-reanimated');
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
result.current.scaleIn();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(withSpring).toHaveBeenCalledWith(1, {
|
|
60
|
+
damping: ANIMATION_CONSTANTS.SPRING.DAMPING,
|
|
61
|
+
stiffness: ANIMATION_CONSTANTS.SPRING.STIFFNESS,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should use custom config when provided', () => {
|
|
66
|
+
const { result } = renderHook(() => useSpringAnimation());
|
|
67
|
+
const { withSpring } = require('react-native-reanimated');
|
|
68
|
+
const customConfig = { damping: 20, stiffness: 200 };
|
|
69
|
+
|
|
70
|
+
act(() => {
|
|
71
|
+
result.current.scaleIn(customConfig);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(withSpring).toHaveBeenCalledWith(1, customConfig);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('scaleOut', () => {
|
|
79
|
+
it('should animate scale to 0 with default config', () => {
|
|
80
|
+
const { result } = renderHook(() => useSpringAnimation());
|
|
81
|
+
const { withSpring } = require('react-native-reanimated');
|
|
82
|
+
|
|
83
|
+
act(() => {
|
|
84
|
+
result.current.scaleOut();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(withSpring).toHaveBeenCalledWith(0, {
|
|
88
|
+
damping: ANIMATION_CONSTANTS.SPRING.DAMPING,
|
|
89
|
+
stiffness: ANIMATION_CONSTANTS.SPRING.STIFFNESS,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('bounce', () => {
|
|
95
|
+
it('should create bounce animation sequence', () => {
|
|
96
|
+
const { result } = renderHook(() => useSpringAnimation());
|
|
97
|
+
const { withSpring, withSequence } = require('react-native-reanimated');
|
|
98
|
+
const mockConfig = { damping: 5, stiffness: 120 };
|
|
99
|
+
|
|
100
|
+
act(() => {
|
|
101
|
+
result.current.bounce(mockConfig);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(withSequence).toHaveBeenCalledWith(
|
|
105
|
+
withSpring(0.8, mockConfig),
|
|
106
|
+
withSpring(1.2, mockConfig),
|
|
107
|
+
withSpring(1, mockConfig)
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should use default config when none provided', () => {
|
|
112
|
+
const { result } = renderHook(() => useSpringAnimation());
|
|
113
|
+
const { withSpring, withSequence } = require('react-native-reanimated');
|
|
114
|
+
|
|
115
|
+
act(() => {
|
|
116
|
+
result.current.bounce();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(withSequence).toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('shared values', () => {
|
|
124
|
+
it('should expose scale shared value for custom animations', () => {
|
|
125
|
+
const { result } = renderHook(() => useSpringAnimation());
|
|
126
|
+
|
|
127
|
+
expect(result.current.scale).toBeDefined();
|
|
128
|
+
expect(result.current.scale.value).toBe(1);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|