diff --git a/src/greeting.js b/src/greeting.js new file mode 100644 index 0000000..1add5f0 --- /dev/null +++ b/src/greeting.js @@ -0,0 +1,63 @@ +/** + * Greeting Utility Module + * + * Provides a simple greeting message generator with time-based context. + */ + +/** + * Get a greeting message based on the current time of day. + * @param {string} name - The name of the person to greet. + * @param {Date} [date] - Optional date object for testing. + * @returns {string} A formatted greeting message. + */ +function getGreeting(name, date = new Date()) { + if (!name || typeof name !== 'string') { + throw new Error('A valid name string is required'); + } + + const hour = date.getHours(); + let period; + + if (hour >= 5 && hour < 12) { + period = 'morning'; + } else if (hour >= 12 && hour < 17) { + period = 'afternoon'; + } else if (hour >= 17 && hour < 21) { + period = 'evening'; + } else { + period = 'night'; + } + + const greetings = { + morning: 'Good morning', + afternoon: 'Good afternoon', + evening: 'Good evening', + night: 'Good night', + }; + + return `${greetings[period]}, ${name}!`; +} + +/** + * Generate a formal greeting for professional contexts. + * @param {string} name - The name of the person. + * @param {object} [options] - Optional settings. + * @param {string} [options.title] - Professional title (e.g. "Dr.", "Prof."). + * @param {string} [options.timezone] - Timezone identifier for localized greeting. + * @returns {string} A formal greeting message. + */ +function getFormalGreeting(name, options = {}) { + if (!name || typeof name !== 'string') { + throw new Error('A valid name string is required'); + } + + const prefix = options.title ? `${options.title} ` : ''; + const greeting = getGreeting(name, options.timezone + ? new Date(new Date().toLocaleString('en-US', { timeZone: options.timezone })) + : undefined + ); + + return greeting.replace(name, `${prefix}${name}`); +} + +module.exports = { getGreeting, getFormalGreeting }; diff --git a/src/greeting.test.js b/src/greeting.test.js new file mode 100644 index 0000000..4e499b7 --- /dev/null +++ b/src/greeting.test.js @@ -0,0 +1,52 @@ +const { getGreeting, getFormalGreeting } = require('./greeting'); + +describe('getGreeting', () => { + test('should return a morning greeting', () => { + const date = new Date('2026-01-01T08:00:00'); + expect(getGreeting('Alice', date)).toBe('Good morning, Alice!'); + }); + + test('should return an afternoon greeting', () => { + const date = new Date('2026-01-01T14:00:00'); + expect(getGreeting('Bob', date)).toBe('Good afternoon, Bob!'); + }); + + test('should return an evening greeting', () => { + const date = new Date('2026-01-01T18:00:00'); + expect(getGreeting('Charlie', date)).toBe('Good evening, Charlie!'); + }); + + test('should return a night greeting', () => { + const date = new Date('2026-01-01T23:00:00'); + expect(getGreeting('Diana', date)).toBe('Good night, Diana!'); + }); + + test('should throw error for empty name', () => { + expect(() => getGreeting('')).toThrow('A valid name string is required'); + }); + + test('should throw error for non-string name', () => { + expect(() => getGreeting(123)).toThrow('A valid name string is required'); + }); + + test('should use current date when not provided', () => { + const result = getGreeting('Test'); + expect(result).toMatch(/Good (morning|afternoon|evening|night), Test!/); + }); +}); + +describe('getFormalGreeting', () => { + test('should include title in greeting', () => { + const date = new Date('2026-01-01T10:00:00'); + expect(getFormalGreeting('Smith', { title: 'Dr.', date })).toContain('Dr. Smith'); + }); + + test('should work without title', () => { + const date = new Date('2026-01-01T10:00:00'); + expect(getFormalGreeting('Smith', { date })).toBe('Good morning, Smith!'); + }); + + test('should throw error for empty name', () => { + expect(() => getFormalGreeting('')).toThrow('A valid name string is required'); + }); +}); diff --git a/src/math.js b/src/math.js new file mode 100644 index 0000000..18aede8 --- /dev/null +++ b/src/math.js @@ -0,0 +1,47 @@ +/** + * Math Utility Module + * + * Provides common math helper functions. + */ + +/** + * Calculate the factorial of a non-negative integer. + * @param {number} n - Non-negative integer + * @returns {number} Factorial of n + * @throws {Error} If n is negative or not an integer + */ +function factorial(n) { + if (!Number.isInteger(n) || n < 0) { + throw new Error('Input must be a non-negative integer'); + } + if (n === 0 || n === 1) return 1; + let result = 1; + for (let i = 2; i <= n; i++) { + result *= i; + } + return result; +} + +/** + * Calculate the Fibonacci number at position n (0-indexed). + * @param {number} n - Position in the Fibonacci sequence + * @returns {number} The nth Fibonacci number + * @throws {Error} If n is negative or not an integer + */ +function fibonacci(n) { + if (!Number.isInteger(n) || n < 0) { + throw new Error('Input must be a non-negative integer'); + } + if (n === 0) return 0; + if (n === 1) return 1; + let prev = 0; + let curr = 1; + for (let i = 2; i <= n; i++) { + const next = prev + curr; + prev = curr; + curr = next; + } + return curr; +} + +module.exports = { factorial, fibonacci };