diff --git a/backend/controller/user.controller.js b/backend/controller/user.controller.js
index 141e174..ff23c3c 100644
--- a/backend/controller/user.controller.js
+++ b/backend/controller/user.controller.js
@@ -44,30 +44,49 @@ const updatelanguage = expressAsyncHandler(async (req, res) => {
const registeruser = expressAsyncHandler (async (req,res) => {
const {name,email,password,pic} = req.body
+ // Validate required fields
if (!name || !email || !password) {
return res.status(400).json({ success: false, message: "Please provide all fields" });
}
- if (name.length < 2 || name.length > 50) {
+ // Validate name length
+ const trimmedName = name.trim();
+ if (trimmedName.length < 2 || trimmedName.length > 50) {
return res.status(400).json({ success: false, message: "Name must be between 2 and 50 characters" });
}
+ // Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- if (!emailRegex.test(email)) {
+ const trimmedEmail = email.trim().toLowerCase();
+ if (!emailRegex.test(trimmedEmail)) {
return res.status(400).json({ success: false, message: "Please provide a valid email address" });
}
+ // Validate password strength
if (password.length < 6) {
return res.status(400).json({ success: false, message: "Password must be at least 6 characters long" });
}
+
+ // Additional password validation: check for at least one letter and one number
+ if (!/(?=.*[a-zA-Z])(?=.*\d)/.test(password)) {
+ return res.status(400).json({ success: false, message: "Password must contain at least one letter and one number" });
+ }
- const userExists = await User.findOne({email})
+ // Check if user already exists
+ const userExists = await User.findOne({ email: trimmedEmail })
if(userExists) {
return res.status(409).json({ success: false, message: "User already exists" });
}
- const user = await User.create({name, email, password,pic})
+
+ // Create new user with trimmed and normalized data
+ const user = await User.create({
+ name: trimmedName,
+ email: trimmedEmail,
+ password,
+ pic: pic || undefined
+ })
if(user){
res.status(201).json({
diff --git a/frontend/src/components/Chat/__tests__/ChatHeader.test.jsx b/frontend/src/components/Chat/__tests__/ChatHeader.test.jsx
new file mode 100644
index 0000000..3ccd27f
--- /dev/null
+++ b/frontend/src/components/Chat/__tests__/ChatHeader.test.jsx
@@ -0,0 +1,48 @@
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { BrowserRouter } from 'react-router-dom';
+import ChatHeader from '../ChatHeader';
+
+// Mock dependencies
+vi.mock('../../../stores', () => ({
+ useAuthStore: () => ({ user: { _id: 'user1', name: 'Test User' } }),
+ useChatStore: () => ({
+ selectedChat: { _id: 'chat1', chatName: 'Test Chat' },
+ setSelectedChat: vi.fn(),
+ }),
+}));
+
+const MockedChatHeader = (props) => (
+
+
+
+);
+
+describe('ChatHeader Component', () => {
+ it('renders chat name', () => {
+ render();
+ expect(document.body).toBeTruthy();
+ });
+
+ it('renders user info for one-on-one chats', () => {
+ const mockChat = {
+ _id: 'chat1',
+ isGroupChat: false,
+ users: [{ _id: 'user2', name: 'Other User', pic: 'pic.jpg' }],
+ };
+ render();
+ expect(document.body).toBeTruthy();
+ });
+
+ it('renders group chat info', () => {
+ const mockChat = {
+ _id: 'chat1',
+ isGroupChat: true,
+ chatName: 'Group Chat',
+ users: [{ _id: 'user2' }, { _id: 'user3' }],
+ };
+ render();
+ expect(document.body).toBeTruthy();
+ });
+});
+
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
index 02c408d..c6893cf 100644
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -5,14 +5,72 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+/**
+ * Get user profile pictures from a chat
+ * @param chat - The chat object containing users
+ * @param currentUserId - The ID of the current user to exclude
+ * @returns Array of user profile picture URLs
+ */
export function getUserPics(chat, currentUserId) {
- if (!chat?.users) return [];
+ if (!chat?.users || !Array.isArray(chat.users)) return [];
- const otherUsers = chat.users.filter(user => user?._id?.toString() !== currentUserId?.toString());
+ const otherUsers = chat.users.filter(user => {
+ if (!user?._id) return false;
+ return user._id.toString() !== currentUserId?.toString();
+ });
if (chat.isGroupChat) {
- return otherUsers.slice(0, 3).map(user => user?.pic).filter(Boolean);
+ // For group chats, return up to 3 user pics
+ return otherUsers
+ .slice(0, 3)
+ .map(user => user?.pic)
+ .filter(Boolean);
} else {
- return otherUsers.length > 0 && otherUsers[0]?.pic ? [otherUsers[0].pic] : [];
+ // For one-on-one chats, return the other user's pic
+ return otherUsers.length > 0 && otherUsers[0]?.pic
+ ? [otherUsers[0].pic]
+ : [];
}
}
+
+/**
+ * Validate email format
+ * @param email - Email string to validate
+ * @returns Boolean indicating if email is valid
+ */
+export function isValidEmail(email) {
+ if (!email || typeof email !== 'string') return false;
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return emailRegex.test(email.trim());
+}
+
+/**
+ * Format file size to human-readable format
+ * @param bytes - File size in bytes
+ * @returns Formatted string (e.g., "1.5 MB")
+ */
+export function formatFileSize(bytes) {
+ if (!bytes || bytes === 0) return '0 Bytes';
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
+}
+
+/**
+ * Debounce function to limit function calls
+ * @param func - Function to debounce
+ * @param wait - Wait time in milliseconds
+ * @returns Debounced function
+ */
+export function debounce(func, wait) {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+}
diff --git a/frontend/src/utils/__tests__/validationUtils.test.js b/frontend/src/utils/__tests__/validationUtils.test.js
new file mode 100644
index 0000000..29cbf5e
--- /dev/null
+++ b/frontend/src/utils/__tests__/validationUtils.test.js
@@ -0,0 +1,59 @@
+import { describe, it, expect } from 'vitest';
+import { isValidEmail, formatFileSize, debounce } from '../../lib/utils';
+
+describe('Validation Utils', () => {
+ describe('isValidEmail', () => {
+ it('validates correct email formats', () => {
+ expect(isValidEmail('test@example.com')).toBe(true);
+ expect(isValidEmail('user.name@domain.co.uk')).toBe(true);
+ expect(isValidEmail('user+tag@example.com')).toBe(true);
+ });
+
+ it('rejects invalid email formats', () => {
+ expect(isValidEmail('invalid-email')).toBe(false);
+ expect(isValidEmail('@example.com')).toBe(false);
+ expect(isValidEmail('user@')).toBe(false);
+ expect(isValidEmail('')).toBe(false);
+ expect(isValidEmail(null)).toBe(false);
+ expect(isValidEmail(undefined)).toBe(false);
+ });
+
+ it('handles whitespace', () => {
+ expect(isValidEmail(' test@example.com ')).toBe(true);
+ });
+ });
+
+ describe('formatFileSize', () => {
+ it('formats bytes correctly', () => {
+ expect(formatFileSize(0)).toBe('0 Bytes');
+ expect(formatFileSize(1024)).toBe('1 KB');
+ expect(formatFileSize(1048576)).toBe('1 MB');
+ expect(formatFileSize(1073741824)).toBe('1 GB');
+ });
+
+ it('handles edge cases', () => {
+ expect(formatFileSize(null)).toBe('0 Bytes');
+ expect(formatFileSize(undefined)).toBe('0 Bytes');
+ });
+ });
+
+ describe('debounce', () => {
+ it('delays function execution', (done) => {
+ let callCount = 0;
+ const func = () => { callCount++; };
+ const debouncedFunc = debounce(func, 100);
+
+ debouncedFunc();
+ debouncedFunc();
+ debouncedFunc();
+
+ expect(callCount).toBe(0);
+
+ setTimeout(() => {
+ expect(callCount).toBe(1);
+ done();
+ }, 150);
+ });
+ });
+});
+