mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-25 10:47:56 +00:00
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:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user