From 8c4ee87622071b14249e9895d140d7375cad688b Mon Sep 17 00:00:00 2001 From: Torpenn Date: Tue, 8 Apr 2025 23:29:14 +0200 Subject: [PATCH] test: improve and add missings tests --- .gitignore | 2 + app/Timer.tsx | 4 +- app/store/__tests__/TimerContext.test.tsx | 156 +++- app/store/__tests__/TimerStore.test.ts | 66 +- components/shared/Button.tsx | 2 +- components/shared/Card.tsx | 2 +- components/shared/NumberSelector.tsx | 4 +- components/shared/Themed.tsx | 5 - components/shared/__tests__/Button-test.js | 63 -- components/shared/__tests__/Button.test.tsx | 142 +++ .../__tests__/{Card-test.js => Card.test.tsx} | 28 +- ...lector-test.js => NumberSelector.test.tsx} | 12 +- .../{Spacers-test.js => Spacers.test.tsx} | 0 .../__snapshots__/Button-test.js.snap | 202 ----- .../__snapshots__/Button.test.tsx.snap | 823 ++++++++++++++++++ .../{Card-test.js.snap => Card.test.tsx.snap} | 0 ...t.js.snap => NumberSelector.test.tsx.snap} | 0 ...ers-test.js.snap => Spacers.test.tsx.snap} | 0 ...ncStorage-test.js => AsyncStorage.test.ts} | 12 +- ...imeHelpers-test.js => timeHelpers.test.ts} | 0 .../timer/business/__tests__/useAudio.test.ts | 83 ++ .../__tests__/useNotification.test.ts | 162 ++++ .../useCases/timer/view/TimerContent.tsx | 6 +- .../view/__tests__/FinishContent.test.tsx | 90 ++ .../view/__tests__/TimerContent.test.tsx | 184 ++++ .../__snapshots__/FinishContent.test.tsx.snap | 194 +++++ .../__snapshots__/TimerContent.test.tsx.snap | 412 +++++++++ 27 files changed, 2354 insertions(+), 300 deletions(-) delete mode 100644 components/shared/__tests__/Button-test.js create mode 100644 components/shared/__tests__/Button.test.tsx rename components/shared/__tests__/{Card-test.js => Card.test.tsx} (64%) rename components/shared/__tests__/{NumberSelector-test.js => NumberSelector.test.tsx} (77%) rename components/shared/__tests__/{Spacers-test.js => Spacers.test.tsx} (100%) delete mode 100644 components/shared/__tests__/__snapshots__/Button-test.js.snap create mode 100644 components/shared/__tests__/__snapshots__/Button.test.tsx.snap rename components/shared/__tests__/__snapshots__/{Card-test.js.snap => Card.test.tsx.snap} (100%) rename components/shared/__tests__/__snapshots__/{NumberSelector-test.js.snap => NumberSelector.test.tsx.snap} (100%) rename components/shared/__tests__/__snapshots__/{Spacers-test.js.snap => Spacers.test.tsx.snap} (100%) rename components/shared/business/__tests__/{AsyncStorage-test.js => AsyncStorage.test.ts} (78%) rename components/shared/business/__tests__/{timeHelpers-test.js => timeHelpers.test.ts} (100%) create mode 100644 components/useCases/timer/business/__tests__/useAudio.test.ts create mode 100644 components/useCases/timer/business/__tests__/useNotification.test.ts create mode 100644 components/useCases/timer/view/__tests__/FinishContent.test.tsx create mode 100644 components/useCases/timer/view/__tests__/TimerContent.test.tsx create mode 100644 components/useCases/timer/view/__tests__/__snapshots__/FinishContent.test.tsx.snap create mode 100644 components/useCases/timer/view/__tests__/__snapshots__/TimerContent.test.tsx.snap diff --git a/.gitignore b/.gitignore index ab2708b..48d152a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ # dependencies node_modules/ +coverage/ + # Expo .expo/ dist/ diff --git a/app/Timer.tsx b/app/Timer.tsx index 34ba7f2..af90949 100644 --- a/app/Timer.tsx +++ b/app/Timer.tsx @@ -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} /> )} diff --git a/app/store/__tests__/TimerContext.test.tsx b/app/store/__tests__/TimerContext.test.tsx index 883f93f..215e6af 100644 --- a/app/store/__tests__/TimerContext.test.tsx +++ b/app/store/__tests__/TimerContext.test.tsx @@ -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>).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).mockRestore(); + }); + + it('should handle errors when updating work time', async () => { + // Simulate an error + (timerStore.saveWorkTime as jest.Mock>).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).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>).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).mockRestore(); + }); + + it('should handle errors when updating multiple values at once', async () => { + // Simulate an error + (timerStore.saveState as jest.Mock>).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).mockRestore(); + }); + it('should update multiple values at once', async () => { const { result } = renderHook(() => useTimerContext(), { wrapper: TimerProvider diff --git a/app/store/__tests__/TimerStore.test.ts b/app/store/__tests__/TimerStore.test.ts index a7868e0..b86f157 100644 --- a/app/store/__tests__/TimerStore.test.ts +++ b/app/store/__tests__/TimerStore.test.ts @@ -11,6 +11,27 @@ jest.mock('@react-native-async-storage/async-storage', () => ({ const mockedAsyncStorage = AsyncStorage as jest.Mocked; 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 () => { diff --git a/components/shared/Button.tsx b/components/shared/Button.tsx index 15e30a9..a71f1bc 100644 --- a/components/shared/Button.tsx +++ b/components/shared/Button.tsx @@ -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; diff --git a/components/shared/Card.tsx b/components/shared/Card.tsx index 0e13b36..ec77e2b 100644 --- a/components/shared/Card.tsx +++ b/components/shared/Card.tsx @@ -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; diff --git a/components/shared/NumberSelector.tsx b/components/shared/NumberSelector.tsx index 98f101a..d9c162a 100644 --- a/components/shared/NumberSelector.tsx +++ b/components/shared/NumberSelector.tsx @@ -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); } diff --git a/components/shared/Themed.tsx b/components/shared/Themed.tsx index f4700fc..73bb891 100644 --- a/components/shared/Themed.tsx +++ b/components/shared/Themed.tsx @@ -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'; diff --git a/components/shared/__tests__/Button-test.js b/components/shared/__tests__/Button-test.js deleted file mode 100644 index da2d5d2..0000000 --- a/components/shared/__tests__/Button-test.js +++ /dev/null @@ -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(