219 lines
6.5 KiB
JavaScript
219 lines
6.5 KiB
JavaScript
/**
|
|
* Property-based tests for profile functionality
|
|
* **Feature: miniapp-frontend, Property 9: Birth Year Range**
|
|
* **Feature: miniapp-frontend, Property 10: Photo Upload Limit**
|
|
* **Feature: miniapp-frontend, Property 11: WeChat Field Validation**
|
|
* **Validates: Requirements 4.3, 4.4, 4.6**
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest'
|
|
import * as fc from 'fast-check'
|
|
import { getBirthYearRange } from '../../utils/format.js'
|
|
|
|
/**
|
|
* Helper functions for profile validation
|
|
* These mirror the validation logic in the profile edit page
|
|
*/
|
|
|
|
/**
|
|
* Check if photo upload should be allowed
|
|
* Property 10: Photo Upload Limit
|
|
* @param {number} currentPhotoCount - Current number of photos
|
|
* @returns {boolean} Whether upload is allowed
|
|
*/
|
|
export function canUploadPhoto(currentPhotoCount) {
|
|
return currentPhotoCount < 5
|
|
}
|
|
|
|
/**
|
|
* Check if phone verification button should be enabled
|
|
* Property 11: WeChat Field Validation
|
|
* @param {string} weChatNo - WeChat number
|
|
* @returns {boolean} Whether button should be enabled
|
|
*/
|
|
export function isPhoneVerifyEnabled(weChatNo) {
|
|
return Boolean(weChatNo && weChatNo.trim().length > 0)
|
|
}
|
|
|
|
describe('Profile Property Tests', () => {
|
|
/**
|
|
* Property 9: Birth Year Range
|
|
* *For any* current year Y, the selectable birth years SHALL be in range [Y-50, Y-18],
|
|
* ensuring users are between 18 and 50 years old.
|
|
* **Validates: Requirements 4.3**
|
|
*/
|
|
describe('Property 9: Birth Year Range', () => {
|
|
it('should return birth years that result in ages between 18 and 50', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
// Generate current years between 2020 and 2030
|
|
fc.integer({ min: 2020, max: 2030 }),
|
|
(currentYear) => {
|
|
const birthYears = getBirthYearRange(currentYear)
|
|
|
|
// All birth years should result in valid ages
|
|
return birthYears.every(birthYear => {
|
|
const age = currentYear - birthYear
|
|
return age >= 18 && age <= 50
|
|
})
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should have minimum birth year as currentYear - 50 (oldest allowed)', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer({ min: 2020, max: 2030 }),
|
|
(currentYear) => {
|
|
const birthYears = getBirthYearRange(currentYear)
|
|
const minBirthYear = Math.min(...birthYears)
|
|
|
|
// Minimum birth year should be currentYear - 50
|
|
return minBirthYear === currentYear - 50
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should have maximum birth year as currentYear - 18 (youngest allowed)', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer({ min: 2020, max: 2030 }),
|
|
(currentYear) => {
|
|
const birthYears = getBirthYearRange(currentYear)
|
|
const maxBirthYear = Math.max(...birthYears)
|
|
|
|
// Maximum birth year should be currentYear - 18
|
|
return maxBirthYear === currentYear - 18
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should return exactly 33 birth year options (ages 18-50 inclusive)', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer({ min: 2020, max: 2030 }),
|
|
(currentYear) => {
|
|
const birthYears = getBirthYearRange(currentYear)
|
|
|
|
// Should have 33 options: 50 - 18 + 1 = 33
|
|
return birthYears.length === 33
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should return birth years in descending order (youngest first)', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer({ min: 2020, max: 2030 }),
|
|
(currentYear) => {
|
|
const birthYears = getBirthYearRange(currentYear)
|
|
|
|
// Check descending order
|
|
for (let i = 0; i < birthYears.length - 1; i++) {
|
|
if (birthYears[i] <= birthYears[i + 1]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
})
|
|
|
|
/**
|
|
* Property 10: Photo Upload Limit
|
|
* *For any* photo upload attempt where current photo count is >= 5,
|
|
* the upload SHALL be rejected.
|
|
* **Validates: Requirements 4.4**
|
|
*/
|
|
describe('Property 10: Photo Upload Limit', () => {
|
|
it('should allow upload when photo count is less than 5', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer({ min: 0, max: 4 }),
|
|
(photoCount) => {
|
|
return canUploadPhoto(photoCount) === true
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should reject upload when photo count is 5 or more', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer({ min: 5, max: 100 }),
|
|
(photoCount) => {
|
|
return canUploadPhoto(photoCount) === false
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should have exactly 5 as the boundary for photo limit', () => {
|
|
// Boundary test: 4 photos should allow upload, 5 should not
|
|
expect(canUploadPhoto(4)).toBe(true)
|
|
expect(canUploadPhoto(5)).toBe(false)
|
|
})
|
|
})
|
|
|
|
/**
|
|
* Property 11: WeChat Field Validation
|
|
* *For any* profile form state where WeChat number field is empty,
|
|
* the phone verification button SHALL be disabled.
|
|
* **Validates: Requirements 4.6**
|
|
*/
|
|
describe('Property 11: WeChat Field Validation', () => {
|
|
it('should disable phone verify button when WeChat is empty', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.constantFrom('', null, undefined),
|
|
(weChatNo) => {
|
|
return isPhoneVerifyEnabled(weChatNo) === false
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should disable phone verify button when WeChat is only whitespace', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.stringOf(fc.constantFrom(' ', '\t', '\n'), { minLength: 1, maxLength: 10 }),
|
|
(weChatNo) => {
|
|
return isPhoneVerifyEnabled(weChatNo) === false
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should enable phone verify button when WeChat has non-whitespace content', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
// Generate valid WeChat IDs (alphanumeric, 6-20 chars)
|
|
fc.stringOf(
|
|
fc.constantFrom(...'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-'),
|
|
{ minLength: 1, maxLength: 20 }
|
|
),
|
|
(weChatNo) => {
|
|
return isPhoneVerifyEnabled(weChatNo) === true
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
})
|
|
})
|