Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions __tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
3 changes: 3 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Optional global Jest setup configuration
// Add any global test environment configuration if needed
export {};
111 changes: 24 additions & 87 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -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<string>();

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<string, string>[],
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);
}
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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",
Expand All @@ -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": ["<rootDir>/jest.setup.ts"]
}
}
}