test: improve and add missings tests

main
Torpenn 2025-04-08 23:29:14 +02:00
parent 5de377f0c3
commit 8c4ee87622
No known key found for this signature in database
GPG Key ID: 56C8A89C974E3ED2
27 changed files with 2354 additions and 300 deletions

2
.gitignore vendored
View File

@ -3,6 +3,8 @@
# dependencies
node_modules/
coverage/
# Expo
.expo/
dist/

View File

@ -178,7 +178,7 @@ export default function Timer() {
}
};
const handleContine = () => {
const handleContinue = () => {
setIsRunning(true);
};
@ -211,7 +211,7 @@ export default function Timer() {
previousRep={previousRep}
handleReset={handleReset}
handleStop={handleStop}
handleContine={handleContine}
handleContinue={handleContinue}
/>
)}
</Container>

View File

@ -10,13 +10,15 @@ jest.mock('../TimerStore', () => ({
saveWorkTime: jest.fn(),
saveRestTime: jest.fn(),
saveState: jest.fn(),
savePreparationTime: jest.fn(),
}
}));
const mockTimerState = {
reps: 5,
workTime: 30,
restTime: 10
restTime: 10,
preparationTime: 3
};
describe('TimerContext', () => {
@ -169,6 +171,158 @@ describe('TimerContext', () => {
expect(result.current.timerState.restTime).toBe(15);
});
it('should handle errors when updating rest time', async () => {
// Simulate an error
(timerStore.saveRestTime as jest.Mock<Promise<void>>).mockRejectedValueOnce(new Error('Saving error'));
// Spy on console.error
jest.spyOn(console, 'error').mockImplementation(() => {});
const { result } = renderHook(() => useTimerContext(), {
wrapper: TimerProvider
});
// Wait for initial loading to complete
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
// Attempt to update rest time
await act(async () => {
await result.current.updateRestTime(15);
});
// Verify error was logged
expect(console.error).toHaveBeenCalledWith(
'Error updating rest time:',
expect.any(Error)
);
// Restore console.error
(console.error as jest.MockedFunction<typeof console.error>).mockRestore();
});
it('should handle errors when updating work time', async () => {
// Simulate an error
(timerStore.saveWorkTime as jest.Mock<Promise<void>>).mockRejectedValueOnce(new Error('Saving error'));
// Spy on console.error
jest.spyOn(console, 'error').mockImplementation(() => {});
const { result } = renderHook(() => useTimerContext(), {
wrapper: TimerProvider
});
// Wait for initial loading to complete
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
// Attempt to update work time
await act(async () => {
await result.current.updateWorkTime(45);
});
// Verify error was logged
expect(console.error).toHaveBeenCalledWith(
'Error updating work time:',
expect.any(Error)
);
// Restore console.error
(console.error as jest.MockedFunction<typeof console.error>).mockRestore();
});
it('should update preparation time', async () => {
const { result } = renderHook(() => useTimerContext(), {
wrapper: TimerProvider
});
// Wait for initial loading to complete
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
// Update preparation time
await act(async () => {
await result.current.updatePreparationTime(5);
});
// Verify savePreparationTime was called with the correct value
expect(timerStore.savePreparationTime).toHaveBeenCalledWith(5);
// Verify state was updated
expect(result.current.timerState.preparationTime).toBe(5);
});
it('should handle errors when updating preparation time', async () => {
// Simulate an error
(timerStore.savePreparationTime as jest.Mock<Promise<void>>).mockRejectedValueOnce(new Error('Saving error'));
// Spy on console.error
jest.spyOn(console, 'error').mockImplementation(() => {});
const { result } = renderHook(() => useTimerContext(), {
wrapper: TimerProvider
});
// Wait for initial loading to complete
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
// Attempt to update preparation time
await act(async () => {
await result.current.updatePreparationTime(5);
});
// Verify error was logged
expect(console.error).toHaveBeenCalledWith(
'Error updating preparation time:',
expect.any(Error)
);
// Restore console.error
(console.error as jest.MockedFunction<typeof console.error>).mockRestore();
});
it('should handle errors when updating multiple values at once', async () => {
// Simulate an error
(timerStore.saveState as jest.Mock<Promise<void>>).mockRejectedValueOnce(new Error('Saving error'));
// Spy on console.error
jest.spyOn(console, 'error').mockImplementation(() => {});
const { result } = renderHook(() => useTimerContext(), {
wrapper: TimerProvider
});
// Wait for initial loading to complete
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
const newState = {
reps: 8,
workTime: 60,
restTime: 20
};
// Attempt to update complete state
await act(async () => {
await result.current.updateState(newState);
});
// Verify error was logged
expect(console.error).toHaveBeenCalledWith(
'Error updating state:',
expect.any(Error)
);
// Restore console.error
(console.error as jest.MockedFunction<typeof console.error>).mockRestore();
});
it('should update multiple values at once', async () => {
const { result } = renderHook(() => useTimerContext(), {
wrapper: TimerProvider

View File

@ -11,6 +11,27 @@ jest.mock('@react-native-async-storage/async-storage', () => ({
const mockedAsyncStorage = AsyncStorage as jest.Mocked<typeof AsyncStorage>;
describe('TimerStore', () => {
// Tests pour savePreparationTime method
describe('savePreparationTime', () => {
test('should save preparation time in AsyncStorage', async () => {
await timerStore.savePreparationTime(5);
expect(mockedAsyncStorage.setItem).toHaveBeenCalledWith('timer_preparation_time', '5');
});
test('should handle errors when saving preparation time', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
mockedAsyncStorage.setItem.mockRejectedValueOnce(new Error('AsyncStorage Error'));
await timerStore.savePreparationTime(5);
expect(consoleSpy).toHaveBeenCalledWith(
'Error saving preparation time:',
expect.any(Error)
);
consoleSpy.mockRestore();
});
});
// Clear all mocks after each test
afterEach(() => {
jest.clearAllMocks();
@ -85,7 +106,8 @@ describe('TimerStore', () => {
const state: TimerState = {
reps: 3,
workTime: 40,
restTime: 20
restTime: 20,
preparationTime: 5
};
await timerStore.saveState(state);
@ -93,6 +115,7 @@ describe('TimerStore', () => {
expect(mockedAsyncStorage.setItem).toHaveBeenCalledWith('timer_reps', '3');
expect(mockedAsyncStorage.setItem).toHaveBeenCalledWith('timer_work_time', '40');
expect(mockedAsyncStorage.setItem).toHaveBeenCalledWith('timer_rest_time', '20');
expect(mockedAsyncStorage.setItem).toHaveBeenCalledWith('timer_preparation_time', '5');
});
test('should handle errors when saving state', async () => {
@ -151,6 +174,41 @@ describe('TimerStore', () => {
});
});
// Tests for getPreparationTime method
describe('getPreparationTime', () => {
test('should retrieve preparation time from AsyncStorage', async () => {
mockedAsyncStorage.getItem.mockResolvedValueOnce('5');
const result = await timerStore.getPreparationTime();
expect(result).toBe(5);
expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith('timer_preparation_time');
});
test('should return default value if no data is found', async () => {
mockedAsyncStorage.getItem.mockResolvedValueOnce(null);
const result = await timerStore.getPreparationTime();
expect(result).toBe(3); // Default preparation time value
expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith('timer_preparation_time');
});
test('should handle errors when retrieving preparation time', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
mockedAsyncStorage.getItem.mockRejectedValueOnce(new Error('AsyncStorage Error'));
const result = await timerStore.getPreparationTime();
expect(result).toBe(3); // Default value
expect(consoleSpy).toHaveBeenCalledWith(
'Error retrieving preparation time:',
expect.any(Error)
);
consoleSpy.mockRestore();
});
});
// Tests for getWorkTime method
describe('getWorkTime', () => {
test('should retrieve work time from AsyncStorage', async () => {
@ -227,7 +285,8 @@ describe('TimerStore', () => {
mockedAsyncStorage.getItem
.mockResolvedValueOnce('3') // reps
.mockResolvedValueOnce('40') // workTime
.mockResolvedValueOnce('20'); // restTime
.mockResolvedValueOnce('20') // restTime
.mockResolvedValueOnce('5'); // preparationTime
const result = await timerStore.getState();
@ -235,12 +294,13 @@ describe('TimerStore', () => {
reps: 3,
workTime: 40,
restTime: 20,
preparationTime: 3
preparationTime: 5
});
expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith('timer_reps');
expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith('timer_work_time');
expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith('timer_rest_time');
expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith('timer_preparation_time');
});
test('should handle errors when retrieving state', async () => {

View File

@ -9,7 +9,7 @@ type ButtonColor = 'green' | 'red' | 'grey' | 'white';
type LabelColor = ButtonColor | 'black';
type IconLocation = 'left' | 'right';
interface ButtonProps {
export interface ButtonProps {
label?: string;
color?: ButtonColor;
labelColor?: LabelColor;

View File

@ -2,7 +2,7 @@ import styled from '@emotion/native';
import React, { useCallback } from 'react';
import { TouchableOpacity } from 'react-native';
interface CardProps {
export interface CardProps {
children: React.ReactNode;
backgroundColor: CardBackgroundColor;
testID?: string;

View File

@ -3,7 +3,7 @@ import styled from '@emotion/native';
import { HorizontalSpacer } from './Spacers';
type FinishContentProps = {
export interface NumberSelectorProps {
reps: number;
setReps: (reps: number) => void;
};
@ -11,7 +11,7 @@ type FinishContentProps = {
export default function NumberSelector({
setReps,
reps
}: FinishContentProps) {
}: NumberSelectorProps) {
const addReps: () => void = () => {
setReps(reps + 1);
}

View File

@ -1,8 +1,3 @@
/**
* Learn more about Light and Dark modes:
* https://docs.expo.io/guides/color-schemes/
*/
import { Text as DefaultText, View as DefaultView } from 'react-native';
import Colors from '@/constants/Colors';

View File

@ -1,63 +0,0 @@
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { render } from '@/components/testUtils';
import Button from '@/components/shared/Button';
const renderComponent = ({ status, onPress } = {}) => {
const base = render(<Button label="Press me" status={status} onPress={onPress} />);
return base;
};
describe('[Component] Button', () => {
describe('Ready state', () => {
test('renders correctly', () => {
const button = renderComponent();
expect(button).toMatchSnapshot();
});
it('executes an action when pressing it', () => {
const action = jest.fn();
const button = renderComponent({ onPress: action });
fireEvent.press(button.getByText('Press me'));
expect(action).toHaveBeenCalledWith();
});
});
describe('Disabled state', () => {
test('renders correctly', () => {
const button = renderComponent({ status: 'disabled' });
expect(button).toMatchSnapshot();
});
test('does not do anything when pressing it', () => {
const action = jest.fn();
const button = renderComponent({ status: 'disabled', onPress: action });
fireEvent.press(button.getByText('Press me'));
expect(action).not.toHaveBeenCalled();
});
});
describe('Loading state', () => {
test('renders correctly', () => {
const button = renderComponent({ status: 'loading' });
expect(button).toMatchSnapshot();
});
test('does not do anything when pressing it', () => {
const action = jest.fn();
const button = renderComponent({ status: 'loading', onPress: action });
fireEvent.press(button.getByText('Press me'));
expect(action).not.toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,142 @@
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { render } from '@/components/testUtils';
import Button, { ButtonProps } from '@/components/shared/Button';
const renderComponent = ({
status,
onPress,
secondary,
icon,
iconLocation,
label,
color,
labelColor
}: ButtonProps = {}) => {
const base = render(
<Button
label={label || "Press me"}
status={status}
onPress={onPress}
secondary={secondary}
icon={icon}
iconLocation={iconLocation}
color={color}
labelColor={labelColor}
/>
);
return base;
};
describe('[Component] Button', () => {
describe('Ready state', () => {
test('renders correctly', () => {
const button = renderComponent();
expect(button).toMatchSnapshot();
});
it('executes an action when pressing it', () => {
const action = jest.fn();
const button = renderComponent({ onPress: action });
fireEvent.press(button.getByText('Press me'));
expect(action).toHaveBeenCalledWith();
});
});
describe('Disabled state', () => {
test('renders correctly', () => {
const button = renderComponent({ status: 'disabled' });
expect(button).toMatchSnapshot();
});
test('does not do anything when pressing it', () => {
const action = jest.fn();
const button = renderComponent({ status: 'disabled', onPress: action });
fireEvent.press(button.getByText('Press me'));
expect(action).not.toHaveBeenCalled();
});
});
describe('Loading state', () => {
test('renders correctly', () => {
const button = renderComponent({ status: 'loading' });
expect(button).toMatchSnapshot();
});
test('does not do anything when pressing it', () => {
const action = jest.fn();
const button = renderComponent({ status: 'loading', onPress: action });
fireEvent.press(button.getByText('Press me'));
expect(action).not.toHaveBeenCalled();
});
});
describe('Secondary button', () => {
test('renders correctly', () => {
const button = renderComponent({ secondary: true });
expect(button).toMatchSnapshot();
});
test('with different colors', () => {
const button = renderComponent({
secondary: true,
color: 'green'
});
expect(button).toMatchSnapshot();
});
});
describe('Icon rendering', () => {
const mockIcon = <div data-testid="mock-icon" />;
test('renders with icon on the right', () => {
const button = renderComponent({
icon: mockIcon,
iconLocation: 'right'
});
expect(button).toMatchSnapshot();
});
test('renders with icon on the left', () => {
const button = renderComponent({
icon: mockIcon,
iconLocation: 'left'
});
expect(button).toMatchSnapshot();
});
test('renders icon only without label', () => {
const button = renderComponent({
icon: mockIcon,
label: undefined
});
expect(button).toMatchSnapshot();
});
});
describe('Color variations', () => {
test('renders with different colors', () => {
const greenButton = renderComponent({ color: 'green', labelColor: 'white' });
const redButton = renderComponent({ color: 'red', labelColor: 'white' });
const greyButton = renderComponent({ color: 'grey' });
expect(greenButton).toMatchSnapshot();
expect(redButton).toMatchSnapshot();
expect(greyButton).toMatchSnapshot();
});
});
});

View File

@ -3,14 +3,16 @@ import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { render } from '@/components/testUtils';
import Card from '@/components/shared/Card';
import Card, { CardProps } from '@/components/shared/Card';
import { Text } from 'react-native';
import { theme } from '@/app/shared/theme';
const renderComponent = ({ backgroundColor, onPress = undefined, content = 'content'} = {}) => {
type RenderCardProps = Omit<CardProps, 'children'> & { children?: React.ReactNode };
const renderComponent = ({ backgroundColor, onPress, children = 'content'}: RenderCardProps) => {
const base = render(
<Card backgroundColor={backgroundColor} onPress={onPress} testID='card'>
<Text>{content}</Text>
<Text>{children}</Text>
</Card>
);
@ -25,7 +27,7 @@ describe('[Component] Card', () => {
});
test('renders children correctly', () => {
const { getByText } = renderComponent({ backgroundColor: 'red', content: 'Test Content'});
const { getByText } = renderComponent({ backgroundColor: 'red', children: 'Test Content'});
expect(getByText('Test Content')).toBeTruthy();
});
@ -40,16 +42,30 @@ describe('[Component] Card', () => {
test('does not render as TouchableOpacity when onPress is not provided', () => {
const { queryByTestId } = renderComponent({ backgroundColor: 'red' });
// TouchableOpacity ne doit pas exister
// TouchableOpacity should not exist
expect(queryByTestId('card').props.onPress).toBeUndefined();
});
test('handles undefined onPress gracefully when pressed', () => {
// Ce test vérifie que le comportement est correct quand onPress est undefined
const { getByTestId } = renderComponent({
backgroundColor: 'red',
onPress: undefined,
children: 'Press me safely'
});
// Vérifie qu'aucune erreur n'est levée lors de l'appui
expect(() => {
fireEvent.press(getByTestId('card'));
}).not.toThrow();
});
test('calls onPress when pressed', () => {
const onPressMock = jest.fn();
const { getByTestId } = renderComponent({
backgroundColor: 'green',
onPress: onPressMock,
content: 'Press me'
children: 'Press me'
});
fireEvent.press(getByTestId('card'));

View File

@ -1,9 +1,9 @@
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { render } from '@/components/testUtils';
import NumberSelector from '@/components/shared/NumberSelector';
import NumberSelector, { NumberSelectorProps } from '@/components/shared/NumberSelector';
const renderComponent = ({ reps = 5, setReps = jest.fn() } = {}) => {
const renderComponent = ({ reps = 5, setReps = jest.fn() }: NumberSelectorProps) => {
const base = render(
<NumberSelector reps={reps} setReps={setReps} />
);
@ -13,20 +13,20 @@ const renderComponent = ({ reps = 5, setReps = jest.fn() } = {}) => {
describe('[Component] NumberSelect', () => {
test('renders correctly', () => {
const component = renderComponent();
const component = renderComponent({ reps: 5, setReps: jest.fn() });
expect(component).toMatchSnapshot();
});
test('renders the correct initial reps value', () => {
const { getByText } = renderComponent()
const { getByText } = renderComponent({ reps: 5, setReps: jest.fn() });
expect(getByText('5')).toBeTruthy();
});
test('increments reps when "+" is pressed', () => {
const setRepsMock = jest.fn();
const { getByText } = renderComponent({ setReps: setRepsMock });
const { getByText } = renderComponent({ reps: 5, setReps: setRepsMock });
fireEvent.press(getByText('+'));
@ -35,7 +35,7 @@ describe('[Component] NumberSelect', () => {
test('decrements reps when "-" is pressed', () => {
const setRepsMock = jest.fn();
const { getByText } = renderComponent({ setReps: setRepsMock });
const { getByText } = renderComponent({ reps: 5, setReps: setRepsMock });
fireEvent.press(getByText('-'));

View File

@ -1,202 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`[Component] Button Disabled state renders correctly 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": true,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Loading state renders correctly 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": true,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Ready state renders correctly 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;

View File

@ -0,0 +1,823 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`[Component] Button Color variations renders with different colors 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#3AC26E",
"borderColor": "#3AC26E",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="white"
style={
[
{
"color": "#000000",
},
[
{
"color": "#FFFFFF",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Color variations renders with different colors 2`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FF4141",
"borderColor": "#FF4141",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="white"
style={
[
{
"color": "#000000",
},
[
{
"color": "#FFFFFF",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Color variations renders with different colors 3`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#F9F9F9",
"borderColor": "#F9F9F9",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Disabled state renders correctly 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": true,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Icon rendering renders icon only without label 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
<View
pointerEvents="none"
style={
[
{
"width": 8,
},
undefined,
]
}
widthUnits={2}
/>
<View
style={
[
{
"alignItems": "center",
"aspectRatio": 1,
"height": 24,
"justifyContent": "center",
},
undefined,
]
}
>
<div
data-testid="mock-icon"
/>
</View>
</View>
`;
exports[`[Component] Button Icon rendering renders with icon on the left 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<View
style={
[
{
"alignItems": "center",
"aspectRatio": 1,
"height": 24,
"justifyContent": "center",
},
undefined,
]
}
>
<div
data-testid="mock-icon"
/>
</View>
<View
pointerEvents="none"
style={
[
{
"width": 8,
},
undefined,
]
}
widthUnits={2}
/>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Icon rendering renders with icon on the right 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
<View
pointerEvents="none"
style={
[
{
"width": 8,
},
undefined,
]
}
widthUnits={2}
/>
<View
style={
[
{
"alignItems": "center",
"aspectRatio": 1,
"height": 24,
"justifyContent": "center",
},
undefined,
]
}
>
<div
data-testid="mock-icon"
/>
</View>
</View>
`;
exports[`[Component] Button Loading state renders correctly 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": true,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Ready state renders correctly 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Secondary button renders correctly 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 1,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
bgColor="white"
style={
[
{
"color": "#000000",
},
[
{
"color": "#FFFFFF",
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;
exports[`[Component] Button Secondary button with different colors 1`] = `
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#3AC26E",
"borderColor": "#3AC26E",
"borderRadius": 6,
"borderWidth": 1,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
bgColor="green"
style={
[
{
"color": "#000000",
},
[
{
"color": "#3AC26E",
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Press me
</Text>
</View>
`;

View File

@ -1,12 +1,14 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { saveUserSettings, loadUserSettings } from '../AsyncStorage';
// Mock de AsyncStorage
jest.mock('@react-native-async-storage/async-storage', () => ({
setItem: jest.fn(),
getItem: jest.fn(),
}));
const mockedSetItem = AsyncStorage.setItem as jest.MockedFunction<typeof AsyncStorage.setItem>;
const mockedGetItem = AsyncStorage.getItem as jest.MockedFunction<typeof AsyncStorage.getItem>;
describe('UserSettings functions', () => {
afterEach(() => {
jest.clearAllMocks();
@ -23,7 +25,7 @@ describe('UserSettings functions', () => {
});
test('should throw an error if AsyncStorage.setItem fails', async () => {
AsyncStorage.setItem.mockRejectedValueOnce(new Error('AsyncStorage error'));
mockedSetItem.mockRejectedValueOnce(new Error('AsyncStorage error'));
await expect(saveUserSettings('theme', 'dark')).rejects.toThrow(
'Failed to load the data from AsyncStorage'
@ -36,7 +38,7 @@ describe('UserSettings functions', () => {
const key = 'theme';
const value = 'dark';
AsyncStorage.getItem.mockResolvedValueOnce(value);
mockedGetItem.mockResolvedValueOnce(value);
const result = await loadUserSettings(key);
@ -46,7 +48,7 @@ describe('UserSettings functions', () => {
});
test('should return null if the value does not exist in AsyncStorage', async () => {
AsyncStorage.getItem.mockResolvedValueOnce(null);
mockedGetItem.mockResolvedValueOnce(null);
const result = await loadUserSettings('nonexistent_key');
@ -54,7 +56,7 @@ describe('UserSettings functions', () => {
});
test('should throw an error if AsyncStorage.getItem fails', async () => {
AsyncStorage.getItem.mockRejectedValueOnce(new Error('AsyncStorage error'));
mockedGetItem.mockRejectedValueOnce(new Error('AsyncStorage error'));
await expect(loadUserSettings('theme')).rejects.toThrow(
'Failed to load the data from AsyncStorage'

View File

@ -0,0 +1,83 @@
import { renderHook, act } from '@testing-library/react-native';
import { Audio } from 'expo-av';
import { useAudio } from '../useAudio';
// Mock expo-av modules
jest.mock('expo-av', () => ({
Audio: {
Sound: {
createAsync: jest.fn().mockResolvedValue({ sound: { playAsync: jest.fn(), unloadAsync: jest.fn() } }),
},
setAudioModeAsync: jest.fn().mockResolvedValue({}),
},
}));
describe('useAudio hook', () => {
// Reset mocks after each test
afterEach(() => {
jest.clearAllMocks();
});
it('should configure audio when soundEnabled is true', async () => {
renderHook(() => useAudio('test-sound.mp3', true));
// Check that setAudioModeAsync was called with the right parameters
expect(Audio.setAudioModeAsync).toHaveBeenCalledWith({
allowsRecordingIOS: false,
staysActiveInBackground: true,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
playThroughEarpieceAndroid: false,
});
});
it('should not configure audio when soundEnabled is false', async () => {
renderHook(() => useAudio('test-sound.mp3', false));
// Check that setAudioModeAsync was not called
expect(Audio.setAudioModeAsync).not.toHaveBeenCalled();
});
it('should play sound when playSound is called and soundEnabled is true', async () => {
const { result } = renderHook(() => useAudio('test-sound.mp3', true));
await act(async () => {
await result.current.playSound();
});
// Check that createAsync was called with the correct sound file
expect(Audio.Sound.createAsync).toHaveBeenCalledWith('test-sound.mp3');
});
it('should not play sound when playSound is called and soundEnabled is false', async () => {
const { result } = renderHook(() => useAudio('test-sound.mp3', false));
await act(async () => {
await result.current.playSound();
});
// Check that createAsync was not called
expect(Audio.Sound.createAsync).not.toHaveBeenCalled();
});
it('should unload sound when component is unmounted', async () => {
const mockUnloadAsync = jest.fn();
(Audio.Sound.createAsync as jest.Mock).mockResolvedValueOnce({
sound: {
playAsync: jest.fn(),
unloadAsync: mockUnloadAsync
}
});
const { result, unmount } = renderHook(() => useAudio('test-sound.mp3', true));
await act(async () => {
await result.current.playSound();
});
unmount();
// Check that unloadAsync was called
expect(mockUnloadAsync).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,162 @@
import { renderHook, act } from '@testing-library/react-native';
import { PermissionsAndroid, Platform } from 'react-native';
import PushNotification from 'react-native-push-notification';
import { useNotification } from '../useNotifications';
// Mock des modules natifs
jest.mock('react-native', () => ({
Platform: {
OS: 'android',
Version: 33,
},
PermissionsAndroid: {
request: jest.fn().mockResolvedValue('granted'),
PERMISSIONS: {
POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS',
},
RESULTS: {
GRANTED: 'granted',
},
},
}));
// Mock de react-native-push-notification
jest.mock('react-native-push-notification', () => {
return {
configure: jest.fn(),
createChannel: jest.fn((channel, callback) => callback(true)),
localNotification: jest.fn(),
cancelLocalNotification: jest.fn(),
Importance: {
HIGH: 4,
},
};
});
// Typage des mocks pour TypeScript
const mockPushNotification = PushNotification as jest.Mocked<typeof PushNotification> & {
Importance: { HIGH: number };
};
// Mock de console.log pour éviter de polluer les logs de test
console.log = jest.fn();
describe('useNotification hook', () => {
const notificationId = '123';
const channelInformations = {
channelId: 'testChannel',
channelName: 'Test Channel',
channelDescription: 'Channel for testing',
};
// Réinitialiser les mocks après chaque test
afterEach(() => {
jest.clearAllMocks();
});
it('devrait demander les permissions de notification au montage sur Android >= 33', async () => {
renderHook(() => useNotification(notificationId, channelInformations));
// Vérifier que la demande de permission a été appelée
expect(PermissionsAndroid.request).toHaveBeenCalledWith(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
);
});
it('ne devrait pas demander les permissions sur iOS', async () => {
Platform.OS = 'ios';
renderHook(() => useNotification(notificationId, channelInformations));
expect(PermissionsAndroid.request).not.toHaveBeenCalled();
// Réinitialiser pour les tests suivants
Platform.OS = 'android';
});
it('devrait configurer PushNotification au montage', () => {
renderHook(() => useNotification(notificationId, channelInformations));
expect(mockPushNotification.configure).toHaveBeenCalled();
const configCall = (mockPushNotification.configure as jest.Mock).mock.calls[0][0];
// Vérifier que les fonctions de callback sont définies
expect(typeof configCall.onRegister).toBe('function');
expect(typeof configCall.onNotification).toBe('function');
// Vérifier les permissions
expect(configCall.permissions).toEqual({
alert: true,
badge: true,
sound: true,
});
// Vérifier les autres paramètres
expect(configCall.popInitialNotification).toBe(true);
expect(configCall.requestPermissions).toBe(Platform.OS === 'ios');
});
it('devrait créer un canal de notification sur Android', () => {
renderHook(() => useNotification(notificationId, channelInformations));
expect(mockPushNotification.createChannel).toHaveBeenCalledWith(
{
...channelInformations,
vibrate: true,
importance: mockPushNotification.Importance.HIGH,
},
expect.any(Function)
);
});
it('ne devrait pas créer de canal sur iOS', () => {
Platform.OS = 'ios';
renderHook(() => useNotification(notificationId, channelInformations));
expect(mockPushNotification.createChannel).not.toHaveBeenCalled();
// Réinitialiser pour les tests suivants
Platform.OS = 'android';
});
it('devrait retourner les fonctions updateNotification et cancelNotification', () => {
const { result } = renderHook(() => useNotification(notificationId, channelInformations));
expect(result.current).toHaveProperty('updateNotification');
expect(result.current).toHaveProperty('cancelNotification');
expect(typeof result.current.updateNotification).toBe('function');
expect(typeof result.current.cancelNotification).toBe('function');
});
it('updateNotification devrait appeler localNotification avec les bons paramètres', () => {
const { result } = renderHook(() => useNotification(notificationId, channelInformations));
act(() => {
result.current.updateNotification('Test Title', 'Test Message');
});
expect(mockPushNotification.localNotification).toHaveBeenCalledWith({
channelId: channelInformations.channelId,
id: parseInt(notificationId),
title: 'Test Title',
message: 'Test Message',
playSound: false,
vibrate: false,
ongoing: true,
priority: 'high',
importance: 'high',
onlyAlertOnce: true,
});
});
it('cancelNotification devrait appeler cancelLocalNotification avec le bon ID', () => {
const { result } = renderHook(() => useNotification(notificationId, channelInformations));
act(() => {
result.current.cancelNotification();
});
expect(mockPushNotification.cancelLocalNotification).toHaveBeenCalledWith(notificationId);
});
});

View File

@ -16,7 +16,7 @@ interface TimerContentProps {
currentRep: number;
isRunning: boolean;
handleStop: () => void;
handleContine: () => void;
handleContinue: () => void;
nextRep: () => void;
previousRep: () => void;
bgColor: TimerBgColor;
@ -33,7 +33,7 @@ export default function TimerContent({
currentRep,
isRunning,
handleStop,
handleContine,
handleContinue,
handleReset,
nextRep,
previousRep,
@ -86,7 +86,7 @@ export default function TimerContent({
{isRunning ? (
<Button label={t('stop')} onPress={handleStop} />
) : (
<Button label={t('start')} onPress={handleContine} />
<Button label={t('start')} onPress={handleContinue} />
)}
<ElasticSpacer />

View File

@ -0,0 +1,90 @@
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { render } from '@/components/testUtils';
import FinishContent from '../FinishContent';
// Mock for navigation
const mockGoBack = jest.fn();
jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
useNavigation: () => ({
goBack: mockGoBack,
}),
}));
// Mock for i18n
jest.mock('@/app/i18n/i18n', () => ({
i18n: {
scoped: jest.fn().mockReturnValue((key: string) => {
const translations: Record<string, string> = {
finish: 'Finished',
restart: 'Restart'
};
return translations[key] || key;
}),
t: jest.fn((key: string) => {
if (key === 'back') return 'Back';
return key;
}),
},
}));
describe('[Component] FinishContent', () => {
const defaultProps = {
handleStart: jest.fn(),
handleReset: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
test('matches snapshot', () => {
const tree = render(
<FinishContent {...defaultProps} />
).toJSON();
expect(tree).toMatchSnapshot();
});
test('displays content correctly', () => {
const { getByText } = render(
<FinishContent {...defaultProps} />
);
// Check that "Finished" text is displayed (translated from "finish")
expect(getByText('Finished')).toBeTruthy();
// Check that buttons are displayed
expect(getByText('Restart')).toBeTruthy();
expect(getByText('Back')).toBeTruthy();
});
test('calls handleStart when restart button is clicked', () => {
const { getByText } = render(
<FinishContent {...defaultProps} />
);
// Click the restart button
fireEvent.press(getByText('Restart'));
// Check that handleStart was called
expect(defaultProps.handleStart).toHaveBeenCalledTimes(1);
});
test('calls goBack and handleReset when back button is clicked', () => {
const { getByText } = render(
<FinishContent {...defaultProps} />
);
// Click the back button
fireEvent.press(getByText('Back'));
// Check that navigation.goBack was called
expect(mockGoBack).toHaveBeenCalled();
// Check that handleReset was called
expect(defaultProps.handleReset).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,184 @@
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { render } from '@/components/testUtils';
import TimerContent from '../TimerContent';
import { TimerBgColor } from '@/components/useCases/timer/business/type';
// Mock for navigation
const mockGoBack = jest.fn();
jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
useNavigation: () => ({
goBack: mockGoBack,
}),
}));
// Mock for i18n
jest.mock('@/app/i18n/i18n', () => ({
i18n: {
scoped: jest.fn().mockReturnValue((key: string) => {
const translations: Record<string, string> = {
preparation: 'Préparation',
fight: 'Combat',
rest: 'Repos',
prev: 'Précédent',
next: 'Suivant',
stop: 'Arrêter',
start: 'Démarrer',
preparationDescription: 'Préparez-vous !'
};
return translations[key] || key;
}),
t: jest.fn((key: string) => {
if (key === 'back') return 'Retour';
return key;
}),
},
}));
// Mock for formatTime
jest.mock('@/components/shared/business/timeHelpers', () => ({
formatTime: jest.fn((time: number) => {
if (time === 60000) return '01:00';
return '00:00';
}),
}));
describe('[Component] TimerContent', () => {
const defaultProps = {
isWorkPhase: true,
timeLeft: 60000,
reps: 5,
currentRep: 2,
isRunning: false,
handleStop: jest.fn(),
handleContinue: jest.fn(),
nextRep: jest.fn(),
previousRep: jest.fn(),
bgColor: 'green' as TimerBgColor,
handleReset: jest.fn()
};
beforeEach(() => {
jest.clearAllMocks();
});
test('matches snapshot', () => {
const tree = render(
<TimerContent {...defaultProps} />
).toJSON();
expect(tree).toMatchSnapshot();
});
test('displays work phase content correctly', () => {
const { getByText } = render(
<TimerContent {...defaultProps} />
);
expect(getByText('Combat')).toBeTruthy();
expect(getByText('01:00')).toBeTruthy();
expect(getByText('2 / 5')).toBeTruthy();
expect(getByText('Précédent')).toBeTruthy();
expect(getByText('Démarrer')).toBeTruthy();
expect(getByText('Suivant')).toBeTruthy();
expect(getByText('Retour')).toBeTruthy();
});
test('displays rest phase content correctly', () => {
const { getByText } = render(
<TimerContent {...defaultProps} isWorkPhase={false} />
);
expect(getByText('Repos')).toBeTruthy();
});
test('displays preparation phase content correctly', () => {
const { getByText } = render(
<TimerContent {...defaultProps} isWorkPhase={true} isPreparationPhase={true} />
);
expect(getByText('Préparation')).toBeTruthy();
expect(getByText('Préparez-vous !')).toBeTruthy();
// Previous button should be disabled in preparation phase
expect(getByText('Précédent')).toBeTruthy();
});
test('shows Stop button when timer is running', () => {
const { getByText } = render(
<TimerContent {...defaultProps} isRunning={true} />
);
expect(getByText('Arrêter')).toBeTruthy();
});
test('shows Start button when timer is stopped', () => {
const { getByText } = render(
<TimerContent {...defaultProps} isRunning={false} />
);
expect(getByText('Démarrer')).toBeTruthy();
});
test('calls handleContinue when Start button is clicked', () => {
const { getByText } = render(
<TimerContent {...defaultProps} />
);
fireEvent.press(getByText('Démarrer'));
expect(defaultProps.handleContinue).toHaveBeenCalledTimes(1);
});
test('calls handleStop when Stop button is clicked', () => {
const { getByText } = render(
<TimerContent {...defaultProps} isRunning={true} />
);
fireEvent.press(getByText('Arrêter'));
expect(defaultProps.handleStop).toHaveBeenCalledTimes(1);
});
test('calls previousRep when Previous button is clicked', () => {
const { getByText } = render(
<TimerContent {...defaultProps} />
);
fireEvent.press(getByText('Précédent'));
expect(defaultProps.previousRep).toHaveBeenCalledTimes(1);
});
test('Previous button is disabled in preparation phase', () => {
const { getByText } = render(
<TimerContent {...defaultProps} isPreparationPhase={true} />
);
fireEvent.press(getByText('Précédent'));
expect(defaultProps.previousRep).not.toHaveBeenCalled();
});
test('calls nextRep when Next button is clicked', () => {
const { getByText } = render(
<TimerContent {...defaultProps} />
);
fireEvent.press(getByText('Suivant'));
expect(defaultProps.nextRep).toHaveBeenCalledTimes(1);
});
test('calls goBack and handleReset when Back button is clicked', () => {
const { getByText } = render(
<TimerContent {...defaultProps} />
);
fireEvent.press(getByText('Retour'));
expect(mockGoBack).toHaveBeenCalled();
expect(defaultProps.handleReset).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,194 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`[Component] FinishContent matches snapshot 1`] = `
[
<Text
style={
[
{
"color": "#000000",
},
[
{
"color": "#FFFFFF",
"fontSize": 70,
"fontWeight": "bold",
},
undefined,
],
]
}
>
Finished
</Text>,
<View
heightUnits={8}
pointerEvents="none"
style={
[
{
"height": 32,
},
undefined,
]
}
/>,
<View
style={
[
{
"backgroundColor": "#FFFFFF",
},
[
{
"backgroundColor": "black",
"flexDirection": "row",
},
undefined,
],
]
}
>
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Restart
</Text>
</View>
<View
pointerEvents="none"
style={
[
{
"width": 12,
},
undefined,
]
}
widthUnits={3}
/>
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Back
</Text>
</View>
</View>,
]
`;

View File

@ -0,0 +1,412 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`[Component] TimerContent matches snapshot 1`] = `
[
<Text
isPreparationPhase={false}
style={
[
{
"color": "#000000",
},
[
{
"color": "#FFFFFF",
"fontSize": 50,
"fontWeight": "bold",
},
undefined,
],
]
}
>
Combat
</Text>,
<View
heightUnits={8}
pointerEvents="none"
style={
[
{
"height": 32,
},
undefined,
]
}
/>,
<Text
isPreparationPhase={false}
style={
[
{
"color": "#000000",
},
[
{
"color": "#FFFFFF",
"fontSize": 100,
"fontWeight": "bold",
},
undefined,
],
]
}
>
01:00
</Text>,
<View
heightUnits={8}
pointerEvents="none"
style={
[
{
"height": 32,
},
undefined,
]
}
/>,
<Text
style={
[
{
"color": "#000000",
},
[
{
"color": "#FFFFFF",
"fontSize": 30,
"fontWeight": "bold",
},
undefined,
],
]
}
>
2
/
5
</Text>,
<View
heightUnits={8}
pointerEvents="none"
style={
[
{
"height": 32,
},
undefined,
]
}
/>,
<View
bgColor="green"
style={
[
{
"backgroundColor": "#FFFFFF",
},
[
{
"backgroundColor": "#3AC26E",
"flexDirection": "row",
"justifyContent": "space-between",
},
undefined,
],
]
}
>
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Précédent
</Text>
</View>
<View
pointerEvents="none"
style={
[
{
"flexGrow": 1,
},
undefined,
]
}
/>
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Démarrer
</Text>
</View>
<View
pointerEvents="none"
style={
[
{
"flexGrow": 1,
},
undefined,
]
}
/>
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Suivant
</Text>
</View>
</View>,
<View
heightUnits={4}
pointerEvents="none"
style={
[
{
"height": 16,
},
undefined,
]
}
/>,
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#FFFFFF",
"borderColor": "#FFFFFF",
"borderRadius": 6,
"borderWidth": 0,
"flexDirection": "row",
"height": 48,
"justifyContent": "center",
"opacity": 1,
"paddingHorizontal": 16,
}
}
>
<Text
color="black"
style={
[
{
"color": "#000000",
},
[
{
"color": "#000000",
"fontSize": 16,
"fontWeight": "semibold",
},
undefined,
],
]
}
>
Retour
</Text>
</View>,
]
`;