diff --git a/mobile/src/__tests__/screens/SettingsScreen.FontSize.test.tsx b/mobile/src/__tests__/screens/SettingsScreen.FontSize.test.tsx new file mode 100644 index 00000000..29fc121a --- /dev/null +++ b/mobile/src/__tests__/screens/SettingsScreen.FontSize.test.tsx @@ -0,0 +1,240 @@ +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +// Mock contexts before importing SettingsScreen +jest.mock('../../contexts/ThemeContext', () => require('../../__mocks__/contexts/ThemeContext')); +jest.mock('../../contexts/BiometricAuthContext', () => require('../../__mocks__/contexts/BiometricAuthContext')); +jest.mock('../../contexts/AuthContext', () => require('../../__mocks__/contexts/AuthContext')); + +// Mock Alert +jest.mock('react-native/Libraries/Alert/Alert', () => ({ + alert: jest.fn(), +})); + +import SettingsScreen from '../../screens/SettingsScreen'; +import FontSizeModal from '../../components/FontSizeModal'; +import { MockThemeProvider } from '../../__mocks__/contexts/ThemeContext'; +import { MockBiometricAuthProvider } from '../../__mocks__/contexts/BiometricAuthContext'; +import { MockAuthProvider } from '../../__mocks__/contexts/AuthContext'; + +// Helper to render SettingsScreen with all required providers +const renderSettingsScreen = (themeValue = {}, biometricValue = {}, authValue = {}) => { + return render( + + + + + + + + ); +}; + +// Helper to render FontSizeModal +const renderFontSizeModal = (overrides: any = {}) => { + const mockSetFontSize = overrides.setFontSize || jest.fn().mockResolvedValue(undefined); + const mockOnClose = overrides.onClose || jest.fn(); + + const themeValue = { + fontSize: overrides.fontSize || ('medium' as 'small' | 'medium' | 'large'), + setFontSize: mockSetFontSize, + }; + + const props = { + visible: overrides.visible !== undefined ? overrides.visible : true, + onClose: mockOnClose, + }; + + return { + ...render( + + + + ), + mockSetFontSize, + mockOnClose, + }; +}; + +describe('SettingsScreen - Font Size Feature', () => { + beforeEach(async () => { + jest.clearAllMocks(); + await AsyncStorage.clear(); + }); + + describe('Rendering', () => { + it('should render Font Size button', () => { + const { getByText } = renderSettingsScreen(); + + expect(getByText('Font Size')).toBeTruthy(); + }); + + it('should show current font size in subtitle', () => { + const { getByText } = renderSettingsScreen({ fontSize: 'medium' }); + + expect(getByText('Current: Medium')).toBeTruthy(); + }); + + it('should show Small font size in subtitle', () => { + const { getByText } = renderSettingsScreen({ fontSize: 'small' }); + + expect(getByText('Current: Small')).toBeTruthy(); + }); + + it('should show Large font size in subtitle', () => { + const { getByText } = renderSettingsScreen({ fontSize: 'large' }); + + expect(getByText('Current: Large')).toBeTruthy(); + }); + }); + + describe('Modal Interaction', () => { + it('should open font size modal when button is pressed', async () => { + const { getByText, getByTestId } = renderSettingsScreen(); + + const fontSizeButton = getByText('Font Size').parent?.parent; + expect(fontSizeButton).toBeTruthy(); + + fireEvent.press(fontSizeButton!); + + // Modal should open (we'll test modal rendering separately) + await waitFor(() => { + // Just verify the button was pressable + expect(fontSizeButton).toBeTruthy(); + }); + }); + }); + + describe('Font Scale Application', () => { + it('should display small font scale', () => { + const { getByText } = renderSettingsScreen({ + fontSize: 'small', + fontScale: 0.875, + }); + + // Verify font size is displayed + expect(getByText('Current: Small')).toBeTruthy(); + }); + + it('should display medium font scale', () => { + const { getByText } = renderSettingsScreen({ + fontSize: 'medium', + fontScale: 1.0, + }); + + expect(getByText('Current: Medium')).toBeTruthy(); + }); + + it('should display large font scale', () => { + const { getByText } = renderSettingsScreen({ + fontSize: 'large', + fontScale: 1.125, + }); + + expect(getByText('Current: Large')).toBeTruthy(); + }); + }); + + describe('Persistence', () => { + it('should save font size to AsyncStorage', async () => { + const mockSetFontSize = jest.fn(async (size) => { + await AsyncStorage.setItem('@pezkuwi/font_size', size); + }); + + const { getByText } = renderSettingsScreen({ + fontSize: 'medium', + setFontSize: mockSetFontSize, + }); + + // Simulate selecting a new size + await mockSetFontSize('large'); + + await waitFor(() => { + expect(AsyncStorage.setItem).toHaveBeenCalledWith('@pezkuwi/font_size', 'large'); + }); + }); + + it('should load saved font size on mount', async () => { + await AsyncStorage.setItem('@pezkuwi/font_size', 'large'); + + const { getByText } = renderSettingsScreen({ fontSize: 'large' }); + + expect(getByText('Current: Large')).toBeTruthy(); + }); + }); +}); + +describe('FontSizeModal Component', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Rendering', () => { + it('should render modal when visible', () => { + const { getByText } = renderFontSizeModal({ fontSize: 'medium', visible: true }); + + expect(getByText('Font Size')).toBeTruthy(); + }); + + it('should render all three size options', () => { + const { getByText } = renderFontSizeModal(); + + expect(getByText('Small')).toBeTruthy(); + expect(getByText(/Medium.*Default/i)).toBeTruthy(); + expect(getByText('Large')).toBeTruthy(); + }); + + it('should show checkmark on current size', () => { + const { getByTestId, getByText } = renderFontSizeModal({ fontSize: 'medium' }); + + const mediumOption = getByTestId('font-size-medium'); + expect(mediumOption).toBeTruthy(); + // Checkmark should be visible for medium + expect(getByText('✓')).toBeTruthy(); + }); + }); + + describe('Size Selection', () => { + it('should call setFontSize when Small is pressed', async () => { + const { getByTestId, mockSetFontSize, mockOnClose } = renderFontSizeModal({ + fontSize: 'medium', + }); + + const smallButton = getByTestId('font-size-small'); + fireEvent.press(smallButton); + + await waitFor(() => { + expect(mockSetFontSize).toHaveBeenCalledWith('small'); + expect(mockOnClose).toHaveBeenCalled(); + }); + }); + + it('should call setFontSize when Large is pressed', async () => { + const { getByTestId, mockSetFontSize, mockOnClose } = renderFontSizeModal({ + fontSize: 'medium', + }); + + const largeButton = getByTestId('font-size-large'); + fireEvent.press(largeButton); + + await waitFor(() => { + expect(mockSetFontSize).toHaveBeenCalledWith('large'); + expect(mockOnClose).toHaveBeenCalled(); + }); + }); + }); + + describe('Modal Close', () => { + it('should call onClose when close button is pressed', async () => { + const { getByTestId, mockOnClose } = renderFontSizeModal(); + + const closeButton = getByTestId('font-size-modal-close'); + fireEvent.press(closeButton); + + await waitFor(() => { + expect(mockOnClose).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/mobile/src/components/FontSizeModal.tsx b/mobile/src/components/FontSizeModal.tsx index 7f740aa0..e354ee60 100644 --- a/mobile/src/components/FontSizeModal.tsx +++ b/mobile/src/components/FontSizeModal.tsx @@ -30,14 +30,20 @@ const FontSizeModal: React.FC = ({ visible, onClose }) => { transparent={true} onRequestClose={onClose} > - - - - Font Size - - - - + + e.stopPropagation()}> + + + Font Size + + + + @@ -50,6 +56,7 @@ const FontSizeModal: React.FC = ({ visible, onClose }) => { fontSize === 'small' && styles.sizeOptionSelected, ]} onPress={() => handleSelectSize('small')} + testID="font-size-small" > Small @@ -64,6 +71,7 @@ const FontSizeModal: React.FC = ({ visible, onClose }) => { fontSize === 'medium' && styles.sizeOptionSelected, ]} onPress={() => handleSelectSize('medium')} + testID="font-size-medium" > Medium (Default) @@ -78,6 +86,7 @@ const FontSizeModal: React.FC = ({ visible, onClose }) => { fontSize === 'large' && styles.sizeOptionSelected, ]} onPress={() => handleSelectSize('large')} + testID="font-size-large" > Large @@ -86,8 +95,9 @@ const FontSizeModal: React.FC = ({ visible, onClose }) => { {fontSize === 'large' && } - - + + + ); };