Skip to content

Commit

Permalink
ft(#4):ft-reset-password-flow (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
munezeromicha authored Oct 2, 2024
1 parent 2c20b54 commit 1a47570
Show file tree
Hide file tree
Showing 13 changed files with 3,178 additions and 137 deletions.
83 changes: 57 additions & 26 deletions __tests__/auth/login/UserLogin.test.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,79 @@
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import UserLogin from '@/components/Login/UserLogin';
import { fireEvent, render, waitFor } from '@testing-library/react-native';
import { router } from 'expo-router';
import AsyncStorage from '@react-native-async-storage/async-storage';

describe('<UserLogin />', () => {
const onSubmitMock = jest.fn();
jest.mock('expo-router', () => ({
router: {
push: jest.fn(),
},
}));

jest.mock('@react-native-async-storage/async-storage', () => ({
getItem: jest.fn(() => Promise.resolve('Test Organization')),
}));

describe('UserLogin Component', () => {
const mockOnSubmit = jest.fn().mockResolvedValue({});

beforeEach(() => {
jest.clearAllMocks();
});

test('renders text correctly', () => {
const { getByText } = render(<UserLogin onSubmit={onSubmitMock} />);
it('renders correctly and fetches organization name from AsyncStorage', async () => {
const { getByText } = render(<UserLogin onSubmit={mockOnSubmit} />);

getByText('Welcome to your_organization_name');
getByText('Switch your organization');
getByText('Sign In');
getByText('Forgot Password?');
getByText('Remember me.');
await waitFor(() => expect(AsyncStorage.getItem).toHaveBeenCalledWith('orgName'));
expect(getByText('Welcome to Test Organization')).toBeTruthy();
});

test('displays error messages when validation fails', async () => {
const { getByText, getByPlaceholderText } = render(<UserLogin onSubmit={onSubmitMock} />);
it('submits form successfully when fields are valid', async () => {
const { getByPlaceholderText, getByText } = render(<UserLogin onSubmit={mockOnSubmit} />);

fireEvent.changeText(getByPlaceholderText('Email'), 'test@example.com');
fireEvent.changeText(getByPlaceholderText('Password'), 'ValidPassword');
fireEvent.press(getByText('Sign In'));

await waitFor(() => {
getByText('Please provide your email address');
getByText('Please provide a password');
});
});
await waitFor(() => expect(mockOnSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'ValidPassword',
}));

test('calls onSubmit with correct values', async () => {
const { getByPlaceholderText, getByText } = render(<UserLogin onSubmit={onSubmitMock} />);
expect(mockOnSubmit).toHaveBeenCalledTimes(1);
});

fireEvent.changeText(getByPlaceholderText('Email'), 'test@example.com');
fireEvent.changeText(getByPlaceholderText('Password'), 'password123');
it('shows validation error messages for empty fields', async () => {
const { getByText } = render(<UserLogin onSubmit={mockOnSubmit} />);

fireEvent.press(getByText('Sign In'));

await waitFor(() => {
expect(onSubmitMock).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
expect(onSubmitMock).toHaveBeenCalledTimes(1);
expect(getByText('Email is required')).toBeTruthy();
expect(getByText('Password is required')).toBeTruthy();
});

expect(mockOnSubmit).not.toHaveBeenCalled();
});

it('navigates to reset password when "Forgot Password?" is pressed', () => {
const { getByText } = render(<UserLogin onSubmit={mockOnSubmit} />);

fireEvent.press(getByText('Forgot Password?'));
expect(router.push).toHaveBeenCalledWith('/auth/forgot-password');
});

it('toggles password visibility when eye icon is pressed', () => {
const { getByPlaceholderText, getByTestId } = render(<UserLogin onSubmit={mockOnSubmit} />);

const passwordInput = getByPlaceholderText('Password');
const toggleIcon = getByTestId('password-toggle');

expect(passwordInput.props.secureTextEntry).toBe(true);

fireEvent.press(toggleIcon);
expect(passwordInput.props.secureTextEntry).toBe(false);

fireEvent.press(toggleIcon);
expect(passwordInput.props.secureTextEntry).toBe(true);
});
});
116 changes: 116 additions & 0 deletions __tests__/forgetpassword/forgot-password.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { MockedProvider } from '@apollo/client/testing';
import { useToast } from 'react-native-toast-notifications';
import ResetPassword from '@/app/auth/forgot-password';
import { RESET_PASSWORD_EMAIL } from '@/graphql/mutations/resetPassword';

jest.mock('react-native-toast-notifications', () => ({
useToast: jest.fn(),
}));

const mockShowToast = jest.fn();
useToast.mockReturnValue({ show: mockShowToast });

const mocks = [
{
request: {
query: RESET_PASSWORD_EMAIL,
variables: { email: 'test@example.com' },
},
result: {
data: {},
},
},
];

describe('<ResetPassword />', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('renders correctly', () => {
const { getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<ResetPassword />
</MockedProvider>
);

getByText('Reset Password');
getByText('You will receive an email to proceed with resetting password');
});

test('shows success message after successful email submission', async () => {
const { getByPlaceholderText, getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<ResetPassword />
</MockedProvider>
);

fireEvent.changeText(getByPlaceholderText('Enter Email'), 'test@example.com');
fireEvent.press(getByText('Continue'));

await waitFor(() =>
expect(mockShowToast).toHaveBeenCalledWith('Check your email to proceed!', expect.anything())
);

// Check if the success message is displayed
expect(getByText('Password reset request successful!')).toBeTruthy();
expect(getByText('Please check your email for a link to reset your password!')).toBeTruthy();
});

test('shows error message when email submission fails', async () => {
const errorMock = [
{
request: {
query: RESET_PASSWORD_EMAIL,
variables: { email: 'test@example.com' },
},
error: new Error('Failed to send reset email'),
},
];

const { getByPlaceholderText, getByText } = render(
<MockedProvider mocks={errorMock} addTypename={false}>
<ResetPassword />
</MockedProvider>
);

fireEvent.changeText(getByPlaceholderText('Enter Email'), 'test@example.com');
fireEvent.press(getByText('Continue'));

await waitFor(() =>
expect(mockShowToast).toHaveBeenCalledWith('Failed to send reset email', expect.anything())
);
});

test('displays validation error when submitting empty email', async () => {
const { getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<ResetPassword />
</MockedProvider>
);

fireEvent.press(getByText('Continue'));

// Assuming the validation schema requires an email
await waitFor(() => expect(getByText(/email/i)).toBeTruthy());
});

test('displays loading state while submitting', async () => {
const { getByPlaceholderText, getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<ResetPassword />
</MockedProvider>
);

fireEvent.changeText(getByPlaceholderText('Enter Email'), 'test@example.com');

// Press the button and check if it shows loading state
fireEvent.press(getByText('Continue'));

expect(getByText('Continue').props.state).toBe('Loading');

await waitFor(() => expect(getByText('Check your email to proceed!')).toBeTruthy());
});
});
126 changes: 126 additions & 0 deletions __tests__/forgetpassword/reset-password.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { useToast } from 'react-native-toast-notifications';
import SetNewPassword from '@/app/auth/reset-password';
import { MockedProvider } from '@apollo/client/testing';
import { FORGOT_PASSWORD, VERIFY_RESET_PASSWORD_TOKEN } from '@/graphql/mutations/resetPassword';

jest.mock('react-native-toast-notifications', () => ({
useToast: jest.fn(),
}));

const mockShowToast = jest.fn();
useToast.mockReturnValue({ show: mockShowToast });

const mocks = [
{
request: {
query: VERIFY_RESET_PASSWORD_TOKEN,
variables: { token: 'mockedToken' },
},
result: {
data: {},
},
},
{
request: {
query: FORGOT_PASSWORD,
variables: {
password: 'newPassword123',
confirmPassword: 'newPassword123',
token: 'mockedToken',
},
},
result: {
data: {},
},
},
];

describe('<SetNewPassword />', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('renders correctly', () => {
const { getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<SetNewPassword />
</MockedProvider>
);

getByText('Reset Password');
});

test('toggles password visibility', () => {
const { getByPlaceholderText, getByRole } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<SetNewPassword />
</MockedProvider>
);

const passwordInput = getByPlaceholderText('Password');
const toggleButton = getByRole('button', { name: /eye/i });

expect(passwordInput.props.secureTextEntry).toBe(true);

fireEvent.press(toggleButton);

expect(passwordInput.props.secureTextEntry).toBe(false);

fireEvent.press(toggleButton);

expect(passwordInput.props.secureTextEntry).toBe(true);
});

test('shows error message when form submission fails', async () => {
const errorMock = [
{
request: {
query: FORGOT_PASSWORD,
variables: {
password: 'newPassword123',
confirmPassword: 'newPassword123',
token: 'mockedToken',
},
},
error: new Error('Failed to reset password'),
},
];

const { getByPlaceholderText, getByText } = render(
<MockedProvider mocks={errorMock} addTypename={false}>
<SetNewPassword />
</MockedProvider>
);

fireEvent.changeText(getByPlaceholderText('Password'), 'newPassword123');
fireEvent.changeText(getByPlaceholderText('Confirm Password'), 'newPassword123');

fireEvent.press(getByText('Continue'));

await waitFor(() =>
expect(mockShowToast).toHaveBeenCalledWith('Failed to reset password', expect.anything())
);
});

test('submits the form with valid inputs', async () => {
const { getByPlaceholderText, getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<SetNewPassword />
</MockedProvider>
);

fireEvent.changeText(getByPlaceholderText('Password'), 'newPassword123');
fireEvent.changeText(getByPlaceholderText('Confirm Password'), 'newPassword123');

fireEvent.press(getByText('Continue'));

await waitFor(() =>
expect(mockShowToast).toHaveBeenCalledWith(
'You have successfully reset your password!',
expect.anything()
)
);
});
});
2 changes: 1 addition & 1 deletion app/(onboarding)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default function AppOnboarding() {
</View>
<View className={`flex-1 flex-row justify-center items-center ${bgColor}`}>
<TouchableOpacity>
<Text className="text-lg font-Inter-Medium" onPress={() => router.push('/auth/login')}>
<Text className={`text-lg font-Inter-Medium ${textColor}`} onPress={() => router.push('/auth/login')}>
Get Started
</Text>
</TouchableOpacity>
Expand Down
3 changes: 3 additions & 0 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Stack } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect } from 'react';
import 'react-native-reanimated';
import { ToastProvider } from 'react-native-toast-notifications';
export { ErrorBoundary } from 'expo-router';

export const unstable_settings = {
Expand Down Expand Up @@ -56,13 +57,15 @@ function RootLayoutNav() {

return (
<ApolloProvider client={client}>
<ToastProvider>
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(onboarding)" options={{ headerShown: false }} />
<Stack.Screen name="auth" options={{ headerShown: false }} />
<Stack.Screen name="dashboard" options={{ headerShown: false }}/>
</Stack>
</ThemeProvider>
</ToastProvider>
</ApolloProvider>
);
}
Loading

0 comments on commit 1a47570

Please sign in to comment.