Skip to content

Commit

Permalink
implementation of signup page and functionality (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
EddyShimwa authored and niyibi250 committed Jun 27, 2024
1 parent ffc18fd commit 65c69c7
Show file tree
Hide file tree
Showing 11 changed files with 1,299 additions and 164 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ module.exports = {
'react/react-in-jsx-scope': 0,
'import/no-extraneous-dependencies': 0,
'import/extensions': 0,
'react/require-default-props': 0,
'no-param-reassign': [
'error',
{ props: true, ignorePropertyModificationsFor: ['state'] },
],
},
ignorePatterns: ['dist/**/*', 'postcss.config.js', 'tailwind.config.js'],
};
844 changes: 715 additions & 129 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,35 @@
},
"dependencies": {
"@reduxjs/toolkit": "^2.2.5",
"@testing-library/user-event": "^14.5.2",
"@types/react-redux": "^7.1.33",
"@types/react-router-dom": "^5.3.3",
"@types/redux-thunk": "^2.1.0",
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"hero-slider": "^3.2.1",
"jest": "^29.7.0",
"formik": "^2.4.6",
"history": "^5.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.2.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"react-spinners": "^0.14.1",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0"
"redux-mock-store": "^1.5.4",
"yup": "^1.4.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.12",
"@types/jest": "^29.5.12",
"@types/jsdom": "^21.1.7",
"@types/mocha": "^10.0.7",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.2.22",
"@types/redux-mock-store": "^1.0.6",
"@types/testing-library__react": "^10.2.0",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"@typescript-eslint/parser": "^7.12.0",
"@vitejs/plugin-react": "^4.2.1",
Expand All @@ -54,6 +60,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"husky": "^9.0.11",
"jest": "^29.7.0",
"jsdom": "^24.1.0",
"lint-staged": "^15.2.5",
"postcss": "^8.4.38",
Expand Down
5 changes: 3 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { BrowserRouter as Router } from 'react-router-dom';
import AppRoutes from './routes/AppRoutes';

function App() {
return (
<div>
<Router>
<AppRoutes />
</div>
</Router>
);
}

Expand Down
190 changes: 190 additions & 0 deletions src/__test__/Auth_Tests/signUp.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import signUpReducer, {
setFirstName,
setLastName,
setEmail,
setPassword,
setUserType,
} from '@/features/Auth/SignUpSlice';
import { store } from '@/app/store';
import SignUp from '@/pages/SignUp';
import HSButton from '@/components/form/HSButton';

const renderSignUp = () => {
render(
<Provider store={store}>
<MemoryRouter initialEntries={['/signup']}>
<Routes>
<Route path="/signup" element={<SignUp />} />
</Routes>
</MemoryRouter>
</Provider>
);
};

describe('signUpSlice', () => {
beforeEach(() => {
configureStore({ reducer: { signUp: signUpReducer } });
});

it('should handle initial state', () => {
const { signUp } = store.getState();
expect(signUp).toEqual({
firstName: '',
lastName: '',
email: '',
password: '',
userType: 'buyer',
loading: false,
error: null,
});
});

it('should handle setFirstName', () => {
store.dispatch(setFirstName('John'));
const { signUp } = store.getState();
expect(signUp.firstName).toEqual('John');
});

it('should handle setLastName', () => {
store.dispatch(setLastName('Doe'));
const { signUp } = store.getState();
expect(signUp.lastName).toEqual('Doe');
});

it('should handle setEmail', () => {
store.dispatch(setEmail('john.doe@example.com'));
const { signUp } = store.getState();
expect(signUp.email).toEqual('john.doe@example.com');
});

it('should handle setPassword', () => {
store.dispatch(setPassword('password123'));
const { signUp } = store.getState();
expect(signUp.password).toEqual('password123');
});

it('should handle setUserType', () => {
store.dispatch(setUserType('vendor'));
const { signUp } = store.getState();
expect(signUp.userType).toEqual('vendor');
});
});

describe('HSButton', () => {
beforeEach(() => {
renderSignUp();
});

it('renders the button with the correct title', async () => {
render(<HSButton title="Test Button" />);
expect(await screen.getByText('Test Button')).toBeInTheDocument();
});

it('calls the onClick handler when clicked', async () => {
let handleClickCalled = false;
const handleClick = () => {
handleClickCalled = true;
};
render(<HSButton title="Test Button" onClick={handleClick} />);
fireEvent.click(await screen.getByText('Test Button'));
expect(handleClickCalled).toBe(true);
});
});

describe('SignUp', () => {
beforeEach(() => {
renderSignUp();
});

it('submits the form with user details', async () => {
const firstNameInput = screen.getByLabelText(/first name/i);
const lastNameInput = screen.getByLabelText(/last name/i);
const emailInput = screen.getByLabelText(/enter your email/i);
const passwordInput = screen.getByLabelText(/enter your password/i);
const confirmPasswordInput = screen.getByLabelText(
/confirm your password/i
);
const userTypeBuyer = screen.getByLabelText('I am a customer');

fireEvent.change(firstNameInput, { target: { value: 'John' } });
fireEvent.change(lastNameInput, { target: { value: 'Doe' } });
fireEvent.change(emailInput, { target: { value: 'johndoe@example.com' } });
fireEvent.change(passwordInput, { target: { value: 'testpassword' } });
fireEvent.change(confirmPasswordInput, {
target: { value: 'testpassword' },
});
fireEvent.click(userTypeBuyer);
});

it('allows user to fill out and submit the form', async () => {
const firstNameInput = screen.getByLabelText(/first name/i);
const lastNameInput = screen.getByLabelText(/last name/i);
const emailInput = screen.getByLabelText(/enter your email/i);
const passwordInput = screen.getByLabelText(/enter your password/i);
const confirmPasswordInput = screen.getByLabelText(
/confirm your password/i
);
const userTypeBuyer = screen.getByLabelText(/i am a customer/i);
const agreeCheckbox = screen.getByLabelText(
/by signing up, i agree with the terms of use & privacy policy\./i
);

fireEvent.change(firstNameInput, { target: { value: 'John' } });
fireEvent.change(lastNameInput, { target: { value: 'Doe' } });
fireEvent.change(emailInput, { target: { value: 'johndoe@example.com' } });
fireEvent.change(passwordInput, { target: { value: 'Test@1234' } });
fireEvent.change(confirmPasswordInput, { target: { value: 'Test@1234' } });
fireEvent.click(userTypeBuyer);
fireEvent.click(agreeCheckbox);

fireEvent.submit(screen.getByRole('form'));

await waitFor(() => {
expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
});
});

it('does not submit the form with incomplete or invalid user details', async () => {
fireEvent.submit(screen.getByRole('form'));

await waitFor(() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});

expect(screen.getByText('First Name is required')).toBeInTheDocument();
expect(screen.getByText('Last Name is required')).toBeInTheDocument();
expect(screen.getByText('Email is required')).toBeInTheDocument();
expect(screen.getByText('Password is required')).toBeInTheDocument();
expect(
screen.getByText('Confirm Password is required')
).toBeInTheDocument();
});

it('submits the form successfully', async () => {
fireEvent.change(screen.getByLabelText(/first name/i), {
target: { value: 'John' },
});
fireEvent.change(screen.getByLabelText(/last name/i), {
target: { value: 'Doe' },
});
fireEvent.change(screen.getByLabelText(/enter your email/i), {
target: { value: 'johndoe@example.com' },
});
fireEvent.change(screen.getByLabelText(/enter your password/i), {
target: { value: 'securepassword' },
});
fireEvent.change(screen.getByLabelText(/confirm your password/i), {
target: { value: 'securepassword' },
});
fireEvent.click(screen.getByLabelText(/i am a customer/i));
fireEvent.submit(screen.getByRole('form'));

await waitFor(() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});
});
});
29 changes: 29 additions & 0 deletions src/__test__/appRouter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { store } from '@/app/store';
import AppRoutes from '@/routes/AppRoutes';

describe('AppRoutes', () => {
it('renders SignUp component on /signup route', async () => {
render(
<Provider store={store}>
<MemoryRouter initialEntries={['/signup']}>
<AppRoutes />
</MemoryRouter>
</Provider>
);
await screen.findAllByLabelText(/first name/i);
});

it('renders ErrorPage component on unknown routes', async () => {
render(
<Provider store={store}>
<MemoryRouter initialEntries={['/unknown-route']}>
<AppRoutes />
</MemoryRouter>
</Provider>
);
await screen.findByText(/not found page/i);
});
});
3 changes: 3 additions & 0 deletions src/app/store.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { configureStore } from '@reduxjs/toolkit';
import signUpReducer from '../features/Auth/SignUpSlice';
import productsSlice from '../features/Popular/RecentProductsSlice';

export const store = configureStore({
reducer: {
signUp: signUpReducer,

Popularproducts: productsSlice,
},
});
Expand Down
46 changes: 26 additions & 20 deletions src/components/form/HSButton.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,51 @@
import React from 'react';
import { Link } from 'react-router-dom';

interface MyButtonProps {
path?: string;
title: string;
title: React.ReactNode;
styles?: string;
click?: () => void;
onClick?: (
e: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>
) => void;
icon?: JSX.Element;
target?: '_blank' | '_self' | '_parent' | '_top';
onChange?: React.ChangeEventHandler<HTMLAnchorElement>;
}

function HSButton({
path = '',
click,
path,
title,
icon,
styles,
target,
onChange,
onClick,
}: MyButtonProps) {
if (path) {
return (
<Link
target={target}
onChange={onChange}
rel="noopener noreferrer"
to={path}
onClick={onClick}
className={`${styles} bg-primary text-white px-6 py-3 rounded-md flex justify-center items-center gap-2 text-sm hover:text-gray-200 hover:shadow-lg hover:scale-105 transition-all duration-300 ease-in-out`}
>
{title} {icon}
</Link>
);
}

return (
<Link
target={target}
onChange={onChange}
rel="noopener noreferrer"
to={path}
onClick={click}
<button
type="button"
onClick={onClick}
className={`${styles} bg-primary text-white px-6 py-3 rounded-md flex justify-center items-center gap-2 text-sm hover:text-gray-200 hover:shadow-lg hover:scale-105 transition-all duration-300 ease-in-out`}
>
{title} {icon}
</Link>
</button>
);
}

HSButton.defaultProps = {
path: '',
styles: '',
click: undefined,
icon: null,
target: '_self',
onChange: undefined,
};

export default HSButton;
Loading

0 comments on commit 65c69c7

Please sign in to comment.