Inline Tests: Test Private Functions

Test internals without exporting them - the killer feature

/#
## Testing Private Functions

This is the killer feature of inline tests:
You can test functions WITHOUT exporting them.

Traditional testing requires you to either:
- Export internal helpers (pollutes your API)
- Test only through public interface (incomplete coverage)
- Use hacks like rewire/proxyquire (brittle)

TJS inline tests have full access to the module scope.
Test everything. Export only what you need.
#/

// ============================================================
// PRIVATE HELPERS (not exported, but fully testable!)
// ============================================================

// Private: Email validation regex
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/

// Private: Validate email format
function isValidEmail(email: ''): false {
  return EMAIL_REGEX.test(email)
}

// Private: Sanitize user input
function sanitize(input: ''): '' {
  return input.trim().toLowerCase()
}

// Private: Generate a unique ID
function generateId(prefix: 'user'):! '' {
  return prefix + '_' + Math.random().toString(36).slice(2, 10)
}

// Private: Hash password (simplified for demo)
function hashPassword(password: ''): 'hashed_0' {
  let hash = 0
  for (let i = 0; i < password.length; i++) {
    hash = ((hash << 5) - hash) + password.charCodeAt(i)
    hash = hash & hash
  }
  return 'hashed_' + Math.abs(hash).toString(16)
}

// Private: Check password strength
function isStrongPassword(password: ''):! { strong: true, issues: [''] } {
  const issues = []
  if (password.length < 8) issues.push('Must be at least 8 characters')
  if (!/[A-Z]/.test(password)) issues.push('Must contain uppercase letter')
  if (!/[a-z]/.test(password)) issues.push('Must contain lowercase letter')
  if (!/[0-9]/.test(password)) issues.push('Must contain a number')
  return { strong: issues.length === 0, issues }
}

// ============================================================
// PUBLIC API (this is all that gets exported)
// ============================================================

export function createUser(input: { email: '', password: '' }):! { id: '', email: '', passwordHash: '' } | { error: '', code: 0 } {

  // Validate email (using private helper)
  const cleanEmail = sanitize(input.email)
  if (!isValidEmail(cleanEmail)) {
    return { error: 'Invalid email format', code: 400 }
  }

  // Validate password (using private helper)
  const strength = isStrongPassword(input.password)
  if (!strength.strong) {
    return { error: strength.issues.join(', '), code: 400 }
  }

  // Create user (using private helpers)
  return {
    id: generateId('user'),
    email: cleanEmail,
    passwordHash: hashPassword(input.password)
  }
}

// ============================================================
// TESTS - Full access to private functions!
// ============================================================

// --- Test private email validation ---
test 'isValidEmail accepts valid emails' {
  expect(isValidEmail('test@example.com')).toBe(true)
  expect(isValidEmail('user.name+tag@domain.co.uk')).toBe(true)
}

test 'isValidEmail rejects invalid emails' {
  expect(isValidEmail('not-an-email')).toBe(false)
  expect(isValidEmail('@nodomain.com')).toBe(false)
  expect(isValidEmail('spaces in@email.com')).toBe(false)
}

// --- Test private sanitization ---
test 'sanitize trims and lowercases' {
  expect(sanitize('  HELLO  ')).toBe('hello')
  expect(sanitize('  Test@Email.COM  ')).toBe('test@email.com')
}

// --- Test private ID generation ---
test 'generateId creates prefixed unique IDs' {
  const id1 = generateId('user')
  const id2 = generateId('user')
  expect(id1.startsWith('user_')).toBe(true)
  expect(id1 !== id2).toBe(true) // unique each time
}

test 'generateId respects prefix' {
  expect(generateId('post').startsWith('post_')).toBe(true)
  expect(generateId('comment').startsWith('comment_')).toBe(true)
}

// --- Test private password hashing ---
test 'hashPassword is deterministic' {
  const hash1 = hashPassword('secret123')
  const hash2 = hashPassword('secret123')
  expect(hash1).toBe(hash2)
}

test 'hashPassword produces different hashes for different inputs' {
  const hash1 = hashPassword('password1')
  const hash2 = hashPassword('password2')
  expect(hash1 !== hash2).toBe(true)
}

// --- Test private password strength checker ---
test 'isStrongPassword rejects weak passwords' {
  const result = isStrongPassword('weak')
  expect(result.strong).toBe(false)
  expect(result.issues.length).toBeGreaterThan(0)
}

test 'isStrongPassword accepts strong passwords' {
  const result = isStrongPassword('MyStr0ngP@ss!')
  expect(result.strong).toBe(true)
  expect(result.issues.length).toBe(0)
}

test 'isStrongPassword lists specific issues' {
  const noUpper = isStrongPassword('lowercase123')
  expect(noUpper.issues).toContain('Must contain uppercase letter')

  const noLower = isStrongPassword('UPPERCASE123')
  expect(noLower.issues).toContain('Must contain lowercase letter')

  const noNumber = isStrongPassword('NoNumbers!')
  expect(noNumber.issues).toContain('Must contain a number')

  const tooShort = isStrongPassword('Ab1!')
  expect(tooShort.issues).toContain('Must be at least 8 characters')
}

// --- Test the public API (integration) ---
test 'createUser validates email' {
  const result = createUser({ email: 'invalid', password: 'StrongPass1!' })
  expect(result.error).toBe('Invalid email format')
}

test 'createUser validates password strength' {
  const result = createUser({ email: 'test@test.com', password: 'weak' })
  expect(result.error).toBeTruthy()
}

test 'createUser succeeds with valid input' {
  const result = createUser({
    email: '  Test@Example.COM  ',
    password: 'MyStr0ngPass!'
  })
  expect(result.id).toBeTruthy()
  expect(result.email).toBe('test@example.com') // sanitized
  expect(result.passwordHash.startsWith('hashed_')).toBe(true)
}

// ============================================================
// DEMO OUTPUT
// ============================================================

console.log('=== Testing Private Functions Demo ===\n')
console.log('The functions isValidEmail, sanitize, generateId,')
console.log('hashPassword, and isStrongPassword are all PRIVATE.')
console.log('They are NOT exported. But we tested them all!\n')

console.log("Try this in Jest/Vitest without exporting them. You can't.")
console.log("You'd have to either pollute your API or leave them untested.\n")

console.log('TJS inline tests: Full coverage. Clean exports.\n')

// Show the public API working
const user = createUser({ email: 'demo@example.com', password: 'SecurePass123!' })
console.log('Created user:', user)