refactor(mobile): Remove i18n, expand core screens, update plan

BREAKING: Removed multi-language support (i18n) - will be re-added later

Changes:
- Removed i18n system (6 language files, LanguageContext)
- Expanded WalletScreen, SettingsScreen, SwapScreen with more features
- Added KurdistanSun component, HEZ/PEZ token icons
- Added EditProfileScreen, WalletSetupScreen
- Added button e2e tests (Profile, Settings, Wallet)
- Updated plan: honest assessment - 42 nav buttons with mock data
- Fixed terminology: Polkadot→Pezkuwi, Substrate→Bizinikiwi

Reality check: UI complete with mock data, converting to production one-by-one
This commit is contained in:
2026-01-15 05:08:21 +03:00
parent 5d293cc954
commit f2e70a8150
110 changed files with 11157 additions and 3260 deletions
@@ -0,0 +1,634 @@
/**
* ProfileButton E2E Tests
*
* Tests the Profile button in BottomTabNavigator and all features
* within ProfileScreen and EditProfileScreen.
*
* Test Coverage:
* - Profile screen rendering and loading state
* - Profile data display (name, email, avatar)
* - Avatar picker modal
* - Edit Profile navigation
* - About Pezkuwi alert
* - Logout flow
* - Referrals navigation
* - EditProfileScreen rendering
* - EditProfileScreen form interactions
* - EditProfileScreen save/cancel flows
*/
import React from 'react';
import { render, fireEvent, waitFor, act } from '@testing-library/react-native';
import { Alert } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
// Mock contexts
jest.mock('../../contexts/ThemeContext', () => require('../../__mocks__/contexts/ThemeContext'));
jest.mock('../../contexts/AuthContext', () => require('../../__mocks__/contexts/AuthContext'));
jest.mock('../../contexts/PezkuwiContext', () => ({
usePezkuwi: () => ({
endpoint: 'wss://rpc.pezkuwichain.io:9944',
setEndpoint: jest.fn(),
api: null,
isApiReady: false,
selectedAccount: null,
}),
}));
// Mock navigation - extended from jest.setup.cjs
const mockNavigate = jest.fn();
const mockGoBack = jest.fn();
jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native');
const ReactModule = require('react');
return {
...actualNav,
useNavigation: () => ({
navigate: mockNavigate,
goBack: mockGoBack,
setOptions: jest.fn(),
addListener: jest.fn(),
removeListener: jest.fn(),
}),
useFocusEffect: (callback: () => (() => void) | void) => {
// Use useEffect to properly handle the callback lifecycle
ReactModule.useEffect(() => {
const unsubscribe = callback();
return unsubscribe;
}, [callback]);
},
};
});
// Mock Alert
const mockAlert = jest.spyOn(Alert, 'alert');
// Mock Supabase with profile data
const mockSupabaseFrom = jest.fn();
const mockProfileData = {
id: 'test-user-id',
full_name: 'Test User',
avatar_url: 'avatar5',
wallet_address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
created_at: '2026-01-01T00:00:00.000Z',
referral_code: 'TESTCODE',
referral_count: 5,
};
jest.mock('../../lib/supabase', () => ({
supabase: {
from: (table: string) => {
mockSupabaseFrom(table);
return {
select: jest.fn().mockReturnThis(),
update: jest.fn().mockReturnThis(),
eq: jest.fn().mockReturnThis(),
single: jest.fn().mockResolvedValue({
data: mockProfileData,
error: null,
}),
};
},
storage: {
from: jest.fn().mockReturnValue({
upload: jest.fn().mockResolvedValue({ data: { path: 'test.jpg' }, error: null }),
getPublicUrl: jest.fn().mockReturnValue({ data: { publicUrl: 'https://test.com/avatar.jpg' } }),
}),
},
},
}));
import ProfileScreen from '../../screens/ProfileScreen';
import EditProfileScreen from '../../screens/EditProfileScreen';
import { MockThemeProvider, mockThemeContext } from '../../__mocks__/contexts/ThemeContext';
import { MockAuthProvider, mockAuthContext } from '../../__mocks__/contexts/AuthContext';
// ============================================================
// TEST HELPERS
// ============================================================
const renderProfileScreen = (overrides: {
theme?: Partial<typeof mockThemeContext>;
auth?: Partial<typeof mockAuthContext>;
} = {}) => {
return render(
<MockAuthProvider value={overrides.auth}>
<MockThemeProvider value={overrides.theme}>
<ProfileScreen />
</MockThemeProvider>
</MockAuthProvider>
);
};
const renderEditProfileScreen = (overrides: {
theme?: Partial<typeof mockThemeContext>;
auth?: Partial<typeof mockAuthContext>;
} = {}) => {
return render(
<MockAuthProvider value={overrides.auth}>
<MockThemeProvider value={overrides.theme}>
<EditProfileScreen />
</MockThemeProvider>
</MockAuthProvider>
);
};
// ============================================================
// TESTS
// ============================================================
describe('ProfileButton E2E Tests', () => {
beforeEach(async () => {
jest.clearAllMocks();
await AsyncStorage.clear();
mockAlert.mockClear();
mockNavigate.mockClear();
mockGoBack.mockClear();
});
// ----------------------------------------------------------
// 1. PROFILE SCREEN RENDERING TESTS
// ----------------------------------------------------------
describe('1. ProfileScreen Rendering', () => {
it('renders Profile screen with main container', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-screen')).toBeTruthy();
});
});
it('renders header gradient section', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-header-gradient')).toBeTruthy();
});
});
it('renders profile cards container', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-cards-container')).toBeTruthy();
});
});
it('renders scroll view', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-scroll-view')).toBeTruthy();
});
});
it('renders footer with version info', async () => {
const { getByTestId, getByText } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-footer')).toBeTruthy();
expect(getByText('Version 1.0.0')).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 2. PROFILE DATA DISPLAY TESTS
// ----------------------------------------------------------
describe('2. Profile Data Display', () => {
it('displays user name from profile data', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
const nameElement = getByTestId('profile-name');
expect(nameElement).toBeTruthy();
});
});
it('displays user email', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
const emailElement = getByTestId('profile-email');
expect(emailElement).toBeTruthy();
});
});
it('displays email card', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-card-email')).toBeTruthy();
});
});
it('displays member since card', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-card-member-since')).toBeTruthy();
});
});
it('displays referrals card with count', async () => {
const { getByTestId, getByText } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-card-referrals')).toBeTruthy();
expect(getByText('5 people')).toBeTruthy();
});
});
it('displays referral code when available', async () => {
const { getByTestId, getByText } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-card-referral-code')).toBeTruthy();
expect(getByText('TESTCODE')).toBeTruthy();
});
});
it('displays wallet address when available', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-card-wallet')).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 3. AVATAR TESTS
// ----------------------------------------------------------
describe('3. Avatar Display and Interaction', () => {
it('renders avatar button', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-avatar-button')).toBeTruthy();
});
});
it('displays emoji avatar when avatar_url is emoji ID', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
// avatar5 = 👩🏻
expect(getByTestId('profile-avatar-emoji-container')).toBeTruthy();
});
});
it('opens avatar picker modal when avatar button is pressed', async () => {
const { getByTestId, getByText } = renderProfileScreen();
await waitFor(() => {
const avatarButton = getByTestId('profile-avatar-button');
fireEvent.press(avatarButton);
});
await waitFor(() => {
// AvatarPickerModal displays "Choose Your Avatar" as title
expect(getByText('Choose Your Avatar')).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 4. ACTION BUTTONS TESTS
// ----------------------------------------------------------
describe('4. Action Buttons', () => {
it('renders Edit Profile button', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-edit-button')).toBeTruthy();
});
});
it('renders About Pezkuwi button', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-about-button')).toBeTruthy();
});
});
it('navigates to EditProfile when Edit Profile button is pressed', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
const editButton = getByTestId('profile-edit-button');
fireEvent.press(editButton);
});
expect(mockNavigate).toHaveBeenCalledWith('EditProfile');
});
it('shows About Pezkuwi alert when button is pressed', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
const aboutButton = getByTestId('profile-about-button');
fireEvent.press(aboutButton);
});
await waitFor(() => {
expect(mockAlert).toHaveBeenCalledWith(
'About Pezkuwi',
expect.stringContaining('Pezkuwi is a decentralized blockchain platform'),
expect.any(Array)
);
});
});
});
// ----------------------------------------------------------
// 5. REFERRALS NAVIGATION TEST
// ----------------------------------------------------------
describe('5. Referrals Navigation', () => {
it('navigates to Referral screen when referrals card is pressed', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
const referralsCard = getByTestId('profile-card-referrals');
fireEvent.press(referralsCard);
});
expect(mockNavigate).toHaveBeenCalledWith('Referral');
});
});
// ----------------------------------------------------------
// 6. LOGOUT TESTS
// ----------------------------------------------------------
describe('6. Logout Flow', () => {
it('renders logout button', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-logout-button')).toBeTruthy();
});
});
it('shows confirmation alert when logout button is pressed', async () => {
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
const logoutButton = getByTestId('profile-logout-button');
fireEvent.press(logoutButton);
});
await waitFor(() => {
expect(mockAlert).toHaveBeenCalledWith(
'Logout',
'Are you sure you want to logout?',
expect.arrayContaining([
expect.objectContaining({ text: 'Cancel' }),
expect.objectContaining({ text: 'Logout', style: 'destructive' }),
])
);
});
});
it('calls signOut when logout is confirmed', async () => {
const mockSignOut = jest.fn();
const { getByTestId } = renderProfileScreen({
auth: { signOut: mockSignOut },
});
await waitFor(() => {
const logoutButton = getByTestId('profile-logout-button');
fireEvent.press(logoutButton);
});
await waitFor(() => {
// Get the alert call arguments
const alertCall = mockAlert.mock.calls[0];
const buttons = alertCall[2];
const logoutAction = buttons.find((b: any) => b.text === 'Logout');
// Simulate pressing Logout
if (logoutAction?.onPress) {
logoutAction.onPress();
}
});
await waitFor(() => {
expect(mockSignOut).toHaveBeenCalled();
});
});
});
// ----------------------------------------------------------
// 7. DARK MODE SUPPORT TESTS
// ----------------------------------------------------------
describe('7. Dark Mode Support', () => {
it('applies dark mode colors when enabled', async () => {
const darkColors = {
background: '#1A1A1A',
surface: '#2A2A2A',
text: '#FFFFFF',
textSecondary: '#CCCCCC',
border: '#404040',
};
const { getByTestId } = renderProfileScreen({
theme: { isDarkMode: true, colors: darkColors },
});
await waitFor(() => {
expect(getByTestId('profile-screen')).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 8. EDIT PROFILE SCREEN RENDERING
// ----------------------------------------------------------
describe('8. EditProfileScreen Rendering', () => {
it('renders EditProfile screen with main container', async () => {
const { getByTestId } = renderEditProfileScreen();
await waitFor(() => {
expect(getByTestId('edit-profile-screen')).toBeTruthy();
});
});
it('renders header with Cancel and Save buttons', async () => {
const { getByTestId, getByText } = renderEditProfileScreen();
await waitFor(() => {
expect(getByTestId('edit-profile-header')).toBeTruthy();
expect(getByTestId('edit-profile-cancel-button')).toBeTruthy();
expect(getByTestId('edit-profile-save-button')).toBeTruthy();
expect(getByText('Edit Profile')).toBeTruthy();
});
});
it('renders avatar section', async () => {
const { getByTestId, getByText } = renderEditProfileScreen();
await waitFor(() => {
expect(getByTestId('edit-profile-avatar-section')).toBeTruthy();
expect(getByText('Change Avatar')).toBeTruthy();
});
});
it('renders name input field', async () => {
const { getByTestId, getByText } = renderEditProfileScreen();
await waitFor(() => {
expect(getByTestId('edit-profile-name-group')).toBeTruthy();
expect(getByTestId('edit-profile-name-input')).toBeTruthy();
expect(getByText('Display Name')).toBeTruthy();
});
});
it('renders read-only email field', async () => {
const { getByTestId, getByText } = renderEditProfileScreen();
await waitFor(() => {
expect(getByTestId('edit-profile-email-group')).toBeTruthy();
expect(getByText('Email cannot be changed')).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 9. EDIT PROFILE FORM INTERACTIONS
// ----------------------------------------------------------
describe('9. EditProfileScreen Form Interactions', () => {
it('allows editing name field', async () => {
const { getByTestId } = renderEditProfileScreen();
await waitFor(() => {
const nameInput = getByTestId('edit-profile-name-input');
fireEvent.changeText(nameInput, 'New Name');
});
});
it('opens avatar modal when avatar button is pressed', async () => {
const { getByTestId, getByText } = renderEditProfileScreen();
await waitFor(() => {
const avatarButton = getByTestId('edit-profile-avatar-button');
fireEvent.press(avatarButton);
});
await waitFor(() => {
expect(getByText('Change Avatar')).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 10. EDIT PROFILE CANCEL FLOW
// ----------------------------------------------------------
describe('10. EditProfileScreen Cancel Flow', () => {
it('goes back without alert when no changes made', async () => {
const { getByTestId } = renderEditProfileScreen();
await waitFor(() => {
const cancelButton = getByTestId('edit-profile-cancel-button');
fireEvent.press(cancelButton);
});
// Should navigate back directly without showing alert
await waitFor(() => {
expect(mockGoBack).toHaveBeenCalled();
});
});
it('shows discard alert when changes exist', async () => {
const { getByTestId } = renderEditProfileScreen();
await waitFor(async () => {
// Make a change
const nameInput = getByTestId('edit-profile-name-input');
fireEvent.changeText(nameInput, 'Changed Name');
// Try to cancel
const cancelButton = getByTestId('edit-profile-cancel-button');
fireEvent.press(cancelButton);
});
await waitFor(() => {
expect(mockAlert).toHaveBeenCalledWith(
'Discard Changes?',
'You have unsaved changes. Are you sure you want to go back?',
expect.arrayContaining([
expect.objectContaining({ text: 'Keep Editing' }),
expect.objectContaining({ text: 'Discard', style: 'destructive' }),
])
);
});
});
});
// ----------------------------------------------------------
// 11. EDIT PROFILE SAVE FLOW
// ----------------------------------------------------------
describe('11. EditProfileScreen Save Flow', () => {
it('Save button is disabled when no changes', async () => {
const { getByTestId } = renderEditProfileScreen();
await waitFor(() => {
const saveButton = getByTestId('edit-profile-save-button');
expect(saveButton).toBeTruthy();
// Save button should have disabled styling when no changes
});
});
it('enables Save button when changes are made', async () => {
const { getByTestId } = renderEditProfileScreen();
await waitFor(async () => {
// Make a change
const nameInput = getByTestId('edit-profile-name-input');
fireEvent.changeText(nameInput, 'New Name Here');
// Save button should now be enabled
const saveButton = getByTestId('edit-profile-save-button');
expect(saveButton).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 12. EDGE CASES
// ----------------------------------------------------------
describe('12. Edge Cases', () => {
it('handles user without profile data gracefully', async () => {
// Override mock to return null profile
jest.doMock('../../lib/supabase', () => ({
supabase: {
from: () => ({
select: jest.fn().mockReturnThis(),
eq: jest.fn().mockReturnThis(),
single: jest.fn().mockResolvedValue({
data: null,
error: null,
}),
}),
},
}));
const { getByTestId } = renderProfileScreen();
await waitFor(() => {
expect(getByTestId('profile-screen')).toBeTruthy();
});
});
it('displays fallback for missing user email', async () => {
const { getByTestId } = renderProfileScreen({
auth: { user: null },
});
// Should handle gracefully
await waitFor(() => {
// Loading state or screen should render
});
});
});
});
@@ -0,0 +1,563 @@
/**
* SettingsButton E2E Tests
*
* Tests the Settings button in DashboardScreen header and all features
* within the SettingsScreen. These tests simulate real user interactions.
*
* Test Coverage:
* - Settings screen rendering
* - Dark Mode toggle
* - Font Size selection
* - Push Notifications toggle
* - Email Updates toggle
* - Network Node selection
* - Biometric Security toggle
* - Auto-Lock Timer selection
* - Profile editing
* - Sign Out flow
* - Support links
*/
import React from 'react';
import { render, fireEvent, waitFor, act } from '@testing-library/react-native';
import { Alert, Linking } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
// Mock contexts
jest.mock('../../contexts/ThemeContext', () => require('../../__mocks__/contexts/ThemeContext'));
jest.mock('../../contexts/BiometricAuthContext', () => require('../../__mocks__/contexts/BiometricAuthContext'));
jest.mock('../../contexts/AuthContext', () => require('../../__mocks__/contexts/AuthContext'));
jest.mock('../../contexts/PezkuwiContext', () => ({
usePezkuwi: () => ({
endpoint: 'wss://rpc.pezkuwichain.io:9944',
setEndpoint: jest.fn(),
api: null,
isApiReady: false,
selectedAccount: null,
}),
}));
// Mock Alert
const mockAlert = jest.spyOn(Alert, 'alert');
// Mock Linking
const mockLinkingOpenURL = jest.spyOn(Linking, 'openURL').mockImplementation(() => Promise.resolve(true));
// Mock Supabase
jest.mock('../../lib/supabase', () => ({
supabase: {
from: jest.fn(() => ({
select: jest.fn().mockReturnThis(),
update: jest.fn().mockReturnThis(),
eq: jest.fn().mockReturnThis(),
maybeSingle: jest.fn().mockResolvedValue({
data: {
id: 'test-user-id',
full_name: 'Test User',
notifications_push: true,
notifications_email: true,
},
error: null,
}),
})),
},
}));
import SettingsScreen from '../../screens/SettingsScreen';
import { MockThemeProvider, mockThemeContext } from '../../__mocks__/contexts/ThemeContext';
import { MockBiometricAuthProvider, mockBiometricContext } from '../../__mocks__/contexts/BiometricAuthContext';
import { MockAuthProvider, mockAuthContext } from '../../__mocks__/contexts/AuthContext';
// ============================================================
// TEST HELPERS
// ============================================================
const renderSettingsScreen = (overrides: {
theme?: Partial<typeof mockThemeContext>;
biometric?: Partial<typeof mockBiometricContext>;
auth?: Partial<typeof mockAuthContext>;
} = {}) => {
return render(
<MockAuthProvider value={overrides.auth}>
<MockBiometricAuthProvider value={overrides.biometric}>
<MockThemeProvider value={overrides.theme}>
<SettingsScreen />
</MockThemeProvider>
</MockBiometricAuthProvider>
</MockAuthProvider>
);
};
// ============================================================
// TESTS
// ============================================================
describe('SettingsButton E2E Tests', () => {
beforeEach(async () => {
jest.clearAllMocks();
await AsyncStorage.clear();
mockAlert.mockClear();
mockLinkingOpenURL.mockClear();
});
// ----------------------------------------------------------
// 1. RENDERING TESTS
// ----------------------------------------------------------
describe('1. Screen Rendering', () => {
it('renders Settings screen with all sections', () => {
const { getByText, getByTestId } = renderSettingsScreen();
// Main container
expect(getByTestId('settings-screen')).toBeTruthy();
// Section headers
expect(getByText('ACCOUNT')).toBeTruthy();
expect(getByText('APP SETTINGS')).toBeTruthy();
expect(getByText('NETWORK & SECURITY')).toBeTruthy();
expect(getByText('SUPPORT')).toBeTruthy();
// Header
expect(getByText('Settings')).toBeTruthy();
});
it('renders all setting items', () => {
const { getByText } = renderSettingsScreen();
// Account section
expect(getByText('Edit Profile')).toBeTruthy();
expect(getByText('Wallet Management')).toBeTruthy();
// App Settings section
expect(getByText('Dark Mode')).toBeTruthy();
expect(getByText('Font Size')).toBeTruthy();
expect(getByText('Push Notifications')).toBeTruthy();
expect(getByText('Email Updates')).toBeTruthy();
// Network & Security section
expect(getByText('Network Node')).toBeTruthy();
expect(getByText('Biometric Security')).toBeTruthy();
expect(getByText('Auto-Lock Timer')).toBeTruthy();
// Support section
expect(getByText('Terms of Service')).toBeTruthy();
expect(getByText('Privacy Policy')).toBeTruthy();
expect(getByText('Help Center')).toBeTruthy();
// Logout
expect(getByText('Sign Out')).toBeTruthy();
});
it('displays version info in footer', () => {
const { getByText } = renderSettingsScreen();
expect(getByText('Pezkuwi Super App v1.0.0')).toBeTruthy();
expect(getByText('© 2026 Digital Kurdistan')).toBeTruthy();
});
});
// ----------------------------------------------------------
// 2. DARK MODE TESTS
// ----------------------------------------------------------
describe('2. Dark Mode Toggle', () => {
it('shows correct subtitle when dark mode is OFF', () => {
const { getByText } = renderSettingsScreen({
theme: { isDarkMode: false },
});
expect(getByText('Light theme enabled')).toBeTruthy();
});
it('shows correct subtitle when dark mode is ON', () => {
const { getByText } = renderSettingsScreen({
theme: { isDarkMode: true },
});
expect(getByText('Dark theme enabled')).toBeTruthy();
});
it('calls toggleDarkMode when switch is toggled', async () => {
const mockToggle = jest.fn();
const { getByTestId } = renderSettingsScreen({
theme: { isDarkMode: false, toggleDarkMode: mockToggle },
});
const darkModeSwitch = getByTestId('dark-mode-switch');
fireEvent(darkModeSwitch, 'valueChange', true);
await waitFor(() => {
expect(mockToggle).toHaveBeenCalledTimes(1);
});
});
});
// ----------------------------------------------------------
// 3. FONT SIZE TESTS
// ----------------------------------------------------------
describe('3. Font Size Selection', () => {
it('shows current font size in subtitle', () => {
const { getByText } = renderSettingsScreen({
theme: { fontSize: 'medium' },
});
expect(getByText('Medium')).toBeTruthy();
});
it('opens font size modal when button is pressed', async () => {
const { getByTestId, getByText } = renderSettingsScreen();
const fontSizeButton = getByTestId('font-size-button');
fireEvent.press(fontSizeButton);
await waitFor(() => {
expect(getByText('Select Font Size')).toBeTruthy();
expect(getByText('Small')).toBeTruthy();
expect(getByText('Large')).toBeTruthy();
});
});
it('calls setFontSize when Small option is selected', async () => {
const mockSetFontSize = jest.fn();
const { getByTestId } = renderSettingsScreen({
theme: { fontSize: 'medium', setFontSize: mockSetFontSize },
});
// Open modal
fireEvent.press(getByTestId('font-size-button'));
// Select Small
await waitFor(() => {
const smallOption = getByTestId('font-size-option-small');
fireEvent.press(smallOption);
});
await waitFor(() => {
expect(mockSetFontSize).toHaveBeenCalledWith('small');
});
});
it('calls setFontSize when Large option is selected', async () => {
const mockSetFontSize = jest.fn();
const { getByTestId } = renderSettingsScreen({
theme: { fontSize: 'medium', setFontSize: mockSetFontSize },
});
// Open modal
fireEvent.press(getByTestId('font-size-button'));
// Select Large
await waitFor(() => {
const largeOption = getByTestId('font-size-option-large');
fireEvent.press(largeOption);
});
await waitFor(() => {
expect(mockSetFontSize).toHaveBeenCalledWith('large');
});
});
it('closes modal when Cancel is pressed', async () => {
const { getByTestId, queryByText } = renderSettingsScreen();
// Open modal
fireEvent.press(getByTestId('font-size-button'));
// Cancel
await waitFor(() => {
const cancelButton = getByTestId('font-size-modal-cancel');
fireEvent.press(cancelButton);
});
// Modal should close (title should not be visible)
await waitFor(() => {
// After closing, modal content should not be rendered
// This is a basic check - in reality modal visibility is controlled by state
});
});
});
// ----------------------------------------------------------
// 4. AUTO-LOCK TIMER TESTS
// ----------------------------------------------------------
describe('4. Auto-Lock Timer Selection', () => {
it('shows current auto-lock time in subtitle', () => {
const { getByText } = renderSettingsScreen({
biometric: { autoLockTimer: 5 },
});
expect(getByText('5 minutes')).toBeTruthy();
});
it('opens auto-lock modal when button is pressed', async () => {
const { getByTestId, getByText } = renderSettingsScreen();
const autoLockButton = getByTestId('auto-lock-button');
fireEvent.press(autoLockButton);
await waitFor(() => {
// Check for modal-specific content
expect(getByText('Lock app after inactivity')).toBeTruthy();
expect(getByText('1 minute')).toBeTruthy();
expect(getByText('15 minutes')).toBeTruthy();
});
});
it('calls setAutoLockTimer when option is selected', async () => {
const mockSetAutoLock = jest.fn();
const { getByTestId } = renderSettingsScreen({
biometric: { autoLockTimer: 5, setAutoLockTimer: mockSetAutoLock },
});
// Open modal
fireEvent.press(getByTestId('auto-lock-button'));
// Select 15 minutes
await waitFor(() => {
const option = getByTestId('auto-lock-option-15');
fireEvent.press(option);
});
await waitFor(() => {
expect(mockSetAutoLock).toHaveBeenCalledWith(15);
});
});
});
// ----------------------------------------------------------
// 5. BIOMETRIC SECURITY TESTS
// ----------------------------------------------------------
describe('5. Biometric Security Toggle', () => {
it('shows "FaceID / Fingerprint" when biometric is disabled', () => {
const { getByText } = renderSettingsScreen({
biometric: { isBiometricEnabled: false },
});
expect(getByText('FaceID / Fingerprint')).toBeTruthy();
});
it('shows biometric type when enabled', () => {
const { getByText } = renderSettingsScreen({
biometric: { isBiometricEnabled: true, biometricType: 'fingerprint' },
});
expect(getByText('Enabled (fingerprint)')).toBeTruthy();
});
it('calls enableBiometric when toggled ON', async () => {
const mockEnable = jest.fn().mockResolvedValue(true);
const { getByTestId } = renderSettingsScreen({
biometric: { isBiometricEnabled: false, enableBiometric: mockEnable },
});
const biometricSwitch = getByTestId('biometric-security-switch');
fireEvent(biometricSwitch, 'valueChange', true);
await waitFor(() => {
expect(mockEnable).toHaveBeenCalled();
});
});
it('calls disableBiometric when toggled OFF', async () => {
const mockDisable = jest.fn();
const { getByTestId } = renderSettingsScreen({
biometric: { isBiometricEnabled: true, disableBiometric: mockDisable },
});
const biometricSwitch = getByTestId('biometric-security-switch');
fireEvent(biometricSwitch, 'valueChange', false);
await waitFor(() => {
expect(mockDisable).toHaveBeenCalled();
});
});
});
// ----------------------------------------------------------
// 6. NETWORK NODE TESTS
// ----------------------------------------------------------
describe('6. Network Node Selection', () => {
it('shows Mainnet in subtitle for production endpoint', () => {
const { getByText } = renderSettingsScreen();
expect(getByText('Mainnet')).toBeTruthy();
});
it('opens network modal when button is pressed', async () => {
const { getByTestId, getByText } = renderSettingsScreen();
const networkButton = getByTestId('network-node-button');
fireEvent.press(networkButton);
await waitFor(() => {
expect(getByText('Select Network Node')).toBeTruthy();
expect(getByText('Pezkuwi Mainnet')).toBeTruthy();
expect(getByText('Pezkuwi Testnet')).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 7. SIGN OUT TESTS
// ----------------------------------------------------------
describe('7. Sign Out Flow', () => {
it('shows confirmation alert when Sign Out is pressed', async () => {
const { getByTestId } = renderSettingsScreen();
const signOutButton = getByTestId('sign-out-button');
fireEvent.press(signOutButton);
await waitFor(() => {
expect(mockAlert).toHaveBeenCalledWith(
'Sign Out',
'Are you sure you want to sign out?',
expect.arrayContaining([
expect.objectContaining({ text: 'Cancel' }),
expect.objectContaining({ text: 'Sign Out', style: 'destructive' }),
])
);
});
});
it('calls signOut when confirmed', async () => {
const mockSignOut = jest.fn();
const { getByTestId } = renderSettingsScreen({
auth: { signOut: mockSignOut },
});
const signOutButton = getByTestId('sign-out-button');
fireEvent.press(signOutButton);
await waitFor(() => {
// Get the alert call arguments
const alertCall = mockAlert.mock.calls[0];
const buttons = alertCall[2];
const signOutAction = buttons.find((b: any) => b.text === 'Sign Out');
// Simulate pressing Sign Out
if (signOutAction?.onPress) {
signOutAction.onPress();
}
expect(mockSignOut).toHaveBeenCalled();
});
});
});
// ----------------------------------------------------------
// 8. SUPPORT LINKS TESTS
// ----------------------------------------------------------
describe('8. Support Links', () => {
it('shows Terms of Service alert when pressed', async () => {
const { getByTestId } = renderSettingsScreen();
const tosButton = getByTestId('terms-of-service-button');
fireEvent.press(tosButton);
await waitFor(() => {
expect(mockAlert).toHaveBeenCalledWith(
'Terms',
'Terms of service content...'
);
});
});
it('shows Privacy Policy alert when pressed', async () => {
const { getByTestId } = renderSettingsScreen();
const privacyButton = getByTestId('privacy-policy-button');
fireEvent.press(privacyButton);
await waitFor(() => {
expect(mockAlert).toHaveBeenCalledWith(
'Privacy',
'Privacy policy content...'
);
});
});
it('opens email client when Help Center is pressed', async () => {
const { getByTestId } = renderSettingsScreen();
const helpButton = getByTestId('help-center-button');
fireEvent.press(helpButton);
await waitFor(() => {
expect(mockLinkingOpenURL).toHaveBeenCalledWith(
'mailto:support@pezkuwichain.io'
);
});
});
});
// ----------------------------------------------------------
// 9. PROFILE EDIT TESTS
// ----------------------------------------------------------
describe('9. Profile Editing', () => {
it('opens profile edit modal when Edit Profile is pressed', async () => {
const { getByTestId, getByText } = renderSettingsScreen();
const editProfileButton = getByTestId('edit-profile-button');
fireEvent.press(editProfileButton);
await waitFor(() => {
// Check for modal-specific content (Full Name and Bio labels)
expect(getByText('Full Name')).toBeTruthy();
expect(getByText('Bio')).toBeTruthy();
});
});
});
// ----------------------------------------------------------
// 10. WALLET MANAGEMENT TESTS
// ----------------------------------------------------------
describe('10. Wallet Management', () => {
it('shows Coming Soon alert when Wallet Management is pressed', async () => {
const { getByTestId } = renderSettingsScreen();
const walletButton = getByTestId('wallet-management-button');
fireEvent.press(walletButton);
await waitFor(() => {
expect(mockAlert).toHaveBeenCalledWith(
'Coming Soon',
'Wallet management screen'
);
});
});
});
// ----------------------------------------------------------
// 11. EDGE CASES
// ----------------------------------------------------------
describe('11. Edge Cases', () => {
it('handles rapid toggle clicks gracefully', async () => {
const mockToggle = jest.fn();
const { getByTestId } = renderSettingsScreen({
theme: { isDarkMode: false, toggleDarkMode: mockToggle },
});
const darkModeSwitch = getByTestId('dark-mode-switch');
// Rapid clicks
fireEvent(darkModeSwitch, 'valueChange', true);
fireEvent(darkModeSwitch, 'valueChange', false);
fireEvent(darkModeSwitch, 'valueChange', true);
await waitFor(() => {
expect(mockToggle).toHaveBeenCalledTimes(3);
});
});
it('displays correctly with all toggles enabled', () => {
const { getByTestId } = renderSettingsScreen({
theme: { isDarkMode: true },
biometric: { isBiometricEnabled: true, biometricType: 'facial' },
});
// All toggles should be visible
expect(getByTestId('dark-mode-switch')).toBeTruthy();
expect(getByTestId('biometric-security-switch')).toBeTruthy();
expect(getByTestId('push-notifications-switch')).toBeTruthy();
expect(getByTestId('email-updates-switch')).toBeTruthy();
});
});
});
@@ -0,0 +1,179 @@
/**
* WalletButton E2E Tests
*
* Tests the Wallet button flow including:
* - WalletSetupScreen choice screen
* - Basic navigation
*/
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { Alert } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
// Mock contexts
jest.mock('../../contexts/ThemeContext', () => require('../../__mocks__/contexts/ThemeContext'));
jest.mock('../../contexts/AuthContext', () => require('../../__mocks__/contexts/AuthContext'));
jest.mock('../../contexts/PezkuwiContext', () => ({
usePezkuwi: () => ({
api: null,
isApiReady: false,
accounts: [],
selectedAccount: null,
connectWallet: jest.fn().mockResolvedValue(undefined),
disconnectWallet: jest.fn(),
createWallet: jest.fn().mockResolvedValue({
address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
mnemonic: 'test test test test test test test test test test test junk',
}),
importWallet: jest.fn().mockResolvedValue({
address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
}),
getKeyPair: jest.fn(),
currentNetwork: 'mainnet',
switchNetwork: jest.fn(),
error: null,
}),
NetworkType: { MAINNET: 'mainnet' },
NETWORKS: { mainnet: { displayName: 'Mainnet', endpoint: 'wss://mainnet.example.com' } },
}));
// Mock @pezkuwi/util-crypto
jest.mock('@pezkuwi/util-crypto', () => ({
mnemonicGenerate: () => 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
mnemonicValidate: () => true,
cryptoWaitReady: jest.fn().mockResolvedValue(true),
}));
// Mock navigation
const mockGoBack = jest.fn();
const mockReplace = jest.fn();
jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
useNavigation: () => ({
navigate: jest.fn(),
goBack: mockGoBack,
replace: mockReplace,
setOptions: jest.fn(),
}),
}));
// Mock Alert
jest.spyOn(Alert, 'alert');
import WalletSetupScreen from '../../screens/WalletSetupScreen';
import { MockThemeProvider } from '../../__mocks__/contexts/ThemeContext';
import { MockAuthProvider } from '../../__mocks__/contexts/AuthContext';
const renderSetup = () => render(
<MockAuthProvider>
<MockThemeProvider>
<WalletSetupScreen />
</MockThemeProvider>
</MockAuthProvider>
);
describe('WalletSetupScreen', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders choice screen', async () => {
const { getByTestId, getByText } = renderSetup();
await waitFor(() => {
expect(getByTestId('wallet-setup-screen')).toBeTruthy();
expect(getByText('Set Up Your Wallet')).toBeTruthy();
});
});
it('shows create button', async () => {
const { getByTestId, getByText } = renderSetup();
await waitFor(() => {
expect(getByTestId('wallet-setup-create-button')).toBeTruthy();
expect(getByText('Create New Wallet')).toBeTruthy();
});
});
it('shows import button', async () => {
const { getByTestId, getByText } = renderSetup();
await waitFor(() => {
expect(getByTestId('wallet-setup-import-button')).toBeTruthy();
expect(getByText('Import Existing Wallet')).toBeTruthy();
});
});
it('close button calls goBack', async () => {
const { getByTestId } = renderSetup();
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-close'));
});
expect(mockGoBack).toHaveBeenCalled();
});
it('create button shows seed phrase screen', async () => {
const { getByTestId, getByText } = renderSetup();
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-create-button'));
});
await waitFor(() => {
expect(getByText('Your Recovery Phrase')).toBeTruthy();
});
});
it('import button shows import screen', async () => {
const { getByTestId, getByText } = renderSetup();
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-import-button'));
});
await waitFor(() => {
expect(getByText('Import Wallet')).toBeTruthy();
});
});
it('seed phrase screen has mnemonic grid', async () => {
const { getByTestId } = renderSetup();
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-create-button'));
});
await waitFor(() => {
expect(getByTestId('mnemonic-grid')).toBeTruthy();
});
});
it('import screen has input field', async () => {
const { getByTestId } = renderSetup();
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-import-button'));
});
await waitFor(() => {
expect(getByTestId('wallet-import-input')).toBeTruthy();
});
});
it('back from seed phrase goes to choice', async () => {
const { getByTestId } = renderSetup();
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-create-button'));
});
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-back'));
});
await waitFor(() => {
expect(getByTestId('wallet-setup-choice')).toBeTruthy();
});
});
it('back from import goes to choice', async () => {
const { getByTestId } = renderSetup();
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-import-button'));
});
await waitFor(() => {
fireEvent.press(getByTestId('wallet-setup-back'));
});
await waitFor(() => {
expect(getByTestId('wallet-setup-choice')).toBeTruthy();
});
});
});
@@ -1,222 +0,0 @@
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 { 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(
<MockAuthProvider value={authValue}>
<MockBiometricAuthProvider value={biometricValue}>
<MockThemeProvider value={themeValue}>
<SettingsScreen />
</MockThemeProvider>
</MockBiometricAuthProvider>
</MockAuthProvider>
);
};
describe('SettingsScreen - Dark Mode Feature', () => {
beforeEach(async () => {
jest.clearAllMocks();
// Clear AsyncStorage before each test
await AsyncStorage.clear();
});
describe('Rendering', () => {
it('should render Dark Mode section with toggle', () => {
const { getByText } = renderSettingsScreen();
expect(getByText('APPEARANCE')).toBeTruthy();
expect(getByText('darkMode')).toBeTruthy();
});
it('should show current dark mode state', () => {
const { getByText } = renderSettingsScreen({ isDarkMode: false });
// Should show subtitle when dark mode is off
expect(getByText(/settingsScreen.subtitles.lightThemeEnabled/i)).toBeTruthy();
});
it('should show "Enabled" when dark mode is on', () => {
const { getByText } = renderSettingsScreen({ isDarkMode: true });
// Should show subtitle when dark mode is on
expect(getByText(/settingsScreen.subtitles.darkThemeEnabled/i)).toBeTruthy();
});
});
describe('Toggle Functionality', () => {
it('should toggle dark mode when switch is pressed', async () => {
const mockToggleDarkMode = jest.fn().mockResolvedValue(undefined);
const { getByTestId } = renderSettingsScreen({
isDarkMode: false,
toggleDarkMode: mockToggleDarkMode,
});
// Find the toggle switch
const darkModeSwitch = getByTestId('dark-mode-switch');
// Toggle the switch
fireEvent(darkModeSwitch, 'valueChange', true);
// Verify toggleDarkMode was called
await waitFor(() => {
expect(mockToggleDarkMode).toHaveBeenCalledTimes(1);
});
});
it('should toggle from enabled to disabled', async () => {
const mockToggleDarkMode = jest.fn().mockResolvedValue(undefined);
const { getByTestId } = renderSettingsScreen({
isDarkMode: true,
toggleDarkMode: mockToggleDarkMode,
});
const darkModeSwitch = getByTestId('dark-mode-switch');
// Toggle off
fireEvent(darkModeSwitch, 'valueChange', false);
await waitFor(() => {
expect(mockToggleDarkMode).toHaveBeenCalledTimes(1);
});
});
});
describe('Persistence', () => {
it('should save dark mode preference to AsyncStorage', async () => {
const mockToggleDarkMode = jest.fn(async () => {
await AsyncStorage.setItem('@pezkuwi/theme', 'dark');
});
const { getByTestId } = renderSettingsScreen({
isDarkMode: false,
toggleDarkMode: mockToggleDarkMode,
});
const darkModeSwitch = getByTestId('dark-mode-switch');
fireEvent(darkModeSwitch, 'valueChange', true);
await waitFor(() => {
expect(AsyncStorage.setItem).toHaveBeenCalledWith('@pezkuwi/theme', 'dark');
});
});
it('should load dark mode preference on mount', async () => {
// Pre-set dark mode in AsyncStorage
await AsyncStorage.setItem('@pezkuwi/theme', 'dark');
const { getByText } = renderSettingsScreen({ isDarkMode: true });
// Verify dark mode is enabled - check for dark theme subtitle
expect(getByText(/settingsScreen.subtitles.darkThemeEnabled/i)).toBeTruthy();
});
});
describe('Theme Application', () => {
it('should apply dark theme colors when enabled', () => {
const darkColors = {
background: '#121212',
surface: '#1E1E1E',
text: '#FFFFFF',
textSecondary: '#B0B0B0',
border: '#333333',
};
const { getByTestId } = renderSettingsScreen({
isDarkMode: true,
colors: darkColors,
});
const container = getByTestId('settings-screen');
// Verify dark background is applied
expect(container.props.style).toEqual(
expect.objectContaining({
backgroundColor: darkColors.background,
})
);
});
it('should apply light theme colors when disabled', () => {
const lightColors = {
background: '#F5F5F5',
surface: '#FFFFFF',
text: '#000000',
textSecondary: '#666666',
border: '#E0E0E0',
};
const { getByTestId } = renderSettingsScreen({
isDarkMode: false,
colors: lightColors,
});
const container = getByTestId('settings-screen');
// Verify light background is applied
expect(container.props.style).toEqual(
expect.objectContaining({
backgroundColor: lightColors.background,
})
);
});
});
describe('Edge Cases', () => {
it('should handle rapid toggle clicks', async () => {
const mockToggleDarkMode = jest.fn().mockResolvedValue(undefined);
const { getByTestId } = renderSettingsScreen({
isDarkMode: false,
toggleDarkMode: mockToggleDarkMode,
});
const darkModeSwitch = getByTestId('dark-mode-switch');
// Rapid clicks
fireEvent(darkModeSwitch, 'valueChange', true);
fireEvent(darkModeSwitch, 'valueChange', false);
fireEvent(darkModeSwitch, 'valueChange', true);
await waitFor(() => {
// Should handle all toggle attempts
expect(mockToggleDarkMode).toHaveBeenCalled();
});
});
it('should call toggleDarkMode multiple times without issues', async () => {
const mockToggleDarkMode = jest.fn().mockResolvedValue(undefined);
const { getByTestId } = renderSettingsScreen({
isDarkMode: false,
toggleDarkMode: mockToggleDarkMode,
});
const darkModeSwitch = getByTestId('dark-mode-switch');
// Toggle multiple times
fireEvent(darkModeSwitch, 'valueChange', true);
fireEvent(darkModeSwitch, 'valueChange', false);
fireEvent(darkModeSwitch, 'valueChange', true);
await waitFor(() => {
// Should handle all calls
expect(mockToggleDarkMode).toHaveBeenCalledTimes(3);
});
});
});
});
@@ -1,240 +0,0 @@
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(
<MockAuthProvider value={authValue}>
<MockBiometricAuthProvider value={biometricValue}>
<MockThemeProvider value={themeValue}>
<SettingsScreen />
</MockThemeProvider>
</MockBiometricAuthProvider>
</MockAuthProvider>
);
};
// 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(
<MockThemeProvider value={themeValue}>
<FontSizeModal {...props} />
</MockThemeProvider>
),
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();
});
});
});
});
+3 -6
View File
@@ -4,7 +4,6 @@ import { render, RenderOptions } from '@testing-library/react-native';
// Mock all contexts with simple implementations
const MockAuthProvider = ({ children }: { children: React.ReactNode }) => <>{children}</>;
const MockPezkuwiProvider = ({ children }: { children: React.ReactNode }) => <>{children}</>;
const MockLanguageProvider = ({ children }: { children: React.ReactNode }) => <>{children}</>;
const MockBiometricAuthProvider = ({ children }: { children: React.ReactNode }) => <>{children}</>;
// Wrapper component with all providers
@@ -12,11 +11,9 @@ const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
return (
<MockAuthProvider>
<MockPezkuwiProvider>
<MockLanguageProvider>
<MockBiometricAuthProvider>
{children}
</MockBiometricAuthProvider>
</MockLanguageProvider>
<MockBiometricAuthProvider>
{children}
</MockBiometricAuthProvider>
</MockPezkuwiProvider>
</MockAuthProvider>
);