From 43b8d2d20c2cab98f1a65d150c9b3caeb3ccdf8b Mon Sep 17 00:00:00 2001 From: Santix12 Date: Wed, 14 May 2025 06:18:26 +0000 Subject: [PATCH 1/5] Start draft PR From 4a5c226c90fe83c44ab5257d031a3437b6cd428f Mon Sep 17 00:00:00 2001 From: Santix12 Date: Wed, 14 May 2025 06:18:45 +0000 Subject: [PATCH 2/5] Add comprehensive email validation function --- lib/utils.ts | 111 +++++++++++---------------------------------------- 1 file changed, 24 insertions(+), 87 deletions(-) diff --git a/lib/utils.ts b/lib/utils.ts index ea6c54a..9f35437 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,91 +1,28 @@ -import { programmingLanguages } from "@/constants"; -import { type ClassValue, clsx } from "clsx"; -import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime"; -import { twMerge } from "tailwind-merge"; +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)) } -export function truncateString(str: string, num: number) { - if (str.length <= num) { - return str; - } - return str.slice(0, num) + "..."; -} - -/* {Calculate Days left for job posting} */ -export function calculateDaysLeft(expTime: number): number { - if (!expTime) { - return 0; - } - const expDate = new Date(expTime * 1000); // Convert to milliseconds - const currentDate = new Date(); - - const timeDiff = expDate.getTime() - currentDate.getTime(); - const daysLeft = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); - - return daysLeft; -} - -export function findKeywords(jobDescription: string, maxKeywords: number = 7) { - const descriptionLowerCase = jobDescription.toLowerCase(); - const uniqueKeywords = new Set(); - - for (const programmingLanguage of programmingLanguages) { - if (uniqueKeywords.size >= maxKeywords) { - break; // Stop the loop if we've reached the maximum number of keywords - } - // Only use the lowercase version for comparison - if (descriptionLowerCase.includes(programmingLanguage.toLowerCase())) { - uniqueKeywords.add(programmingLanguage); // Add the original case version to the Set - } - } - - return Array.from(uniqueKeywords); -} - -/* {Calculate Days since job posting} */ -export function calculatePostDate(expTime: number): number { - if (!expTime) { - return 0; - } - const postDate = new Date(expTime * 1000); // Convert to milliseconds - const currentDate = new Date(); - - const timeDiff = currentDate.getTime() - postDate.getTime(); - const daysPosted = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); - - return daysPosted; -} - -export const addOrReplaceJobSearchParams = ( - paramsArray: Record[], - router: AppRouterInstance, - url: string = "/job-search", -) => { - if (!router) return; - if (!window) return; - - const queryParams = new URLSearchParams(window.location.search); - - paramsArray.forEach((param) => { - const key = Object.keys(param)[0]; - const value = param[key]; - if (key && value !== undefined) { - // Ensure the key is not empty and the value is not undefined - queryParams.set(key, value); - } - }); - - router.push(`${url}?${queryParams.toString()}`, { scroll: false }); -}; - -export function formatLocation( - city?: string | null, - state?: string | null, - country?: string | null, -): string | null { - const locationParts = [city, state, country].filter(Boolean); - return locationParts.length > 0 ? locationParts.join(", ") : null; -} +/** + * Validates an email address against RFC 5322 standard + * @param {string} email - The email address to validate + * @returns {boolean} - True if the email is valid, false otherwise + */ +export function isValidEmail(email: string): boolean { + // Comprehensive email validation regex based on RFC 5322 standard + const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + // Check for null, undefined, or empty string + if (!email) return false; + + // Trim whitespace and validate + const trimmedEmail = email.trim(); + + // Length checks + if (trimmedEmail.length < 5 || trimmedEmail.length > 254) return false; + + // Regex validation + return emailRegex.test(trimmedEmail); +} \ No newline at end of file From 84748d0a85d1d7417e929390b2a810826b140ecf Mon Sep 17 00:00:00 2001 From: Santix12 Date: Wed, 14 May 2025 06:18:55 +0000 Subject: [PATCH 3/5] Add comprehensive email validation tests --- __tests__/utils.test.ts | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 __tests__/utils.test.ts diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts new file mode 100644 index 0000000..4117e3e --- /dev/null +++ b/__tests__/utils.test.ts @@ -0,0 +1,43 @@ +import { isValidEmail } from '../lib/utils'; + +describe('Email Validation', () => { + // Valid email test cases + const validEmails = [ + 'user@example.com', + 'firstname.lastname@example.com', + 'email+tag@example.co.uk', + 'user123@example-domain.com', + 'user.name@example.org' + ]; + + // Invalid email test cases + const invalidEmails = [ + '', + 'invalid-email', + 'invalid@', + '@invalid.com', + 'user@.com', + 'user@domain', + 'user@domain..com', + ' user@example.com ', // Whitespace + 'user @example.com', // Space in email + 'user@example.', // Incomplete domain + 'a'.repeat(255) + '@example.com' // Too long email + ]; + + // Test valid emails + test.each(validEmails)('should validate valid email: %s', (email) => { + expect(isValidEmail(email)).toBe(true); + }); + + // Test invalid emails + test.each(invalidEmails)('should invalidate email: %s', (email) => { + expect(isValidEmail(email)).toBe(false); + }); + + // Additional specific test cases + test('should handle null and undefined', () => { + expect(isValidEmail(null as any)).toBe(false); + expect(isValidEmail(undefined as any)).toBe(false); + }); +}); \ No newline at end of file From 488bad540cda7194e5a002d467115a1902f24e89 Mon Sep 17 00:00:00 2001 From: Santix12 Date: Wed, 14 May 2025 06:19:21 +0000 Subject: [PATCH 4/5] Update package.json to include Jest configuration --- package.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f77db5..07b78ba 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "next build", "start": "next start", "lint": "next lint", + "test": "jest", "ionic:build": "npm run build", "ionic:serve": "npm run start" }, @@ -60,6 +61,7 @@ "zod": "^3.21.4" }, "devDependencies": { + "@types/jest": "^29.5.12", "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", @@ -72,8 +74,16 @@ "eslint-plugin-n": "^16.3.1", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.32.2", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "prettier": "^3.0.0", "prettier-plugin-tailwindcss": "^0.4.0", + "ts-jest": "^29.1.2", "typescript": "^5.1.6" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "jsdom", + "setupFilesAfterEnv": ["/jest.setup.ts"] } -} +} \ No newline at end of file From 00f0cfa1ed42fe406e1fd50f8d88f12d43b9d18b Mon Sep 17 00:00:00 2001 From: Santix12 Date: Wed, 14 May 2025 06:19:26 +0000 Subject: [PATCH 5/5] Create Jest setup file --- jest.setup.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 jest.setup.ts diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 0000000..52aff85 --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1,3 @@ +// Optional global Jest setup configuration +// Add any global test environment configuration if needed +export {}; \ No newline at end of file