WellNuo/utils/__tests__/imageUtils.test.ts
Sergei 74a4c9e8f4 Fix avatar caching after upload with cache-busting
Implemented cache-busting mechanism to prevent stale avatar images
after upload. React Native Image component caches images by URI,
causing old avatars to persist even after successful upload.

Changes:
- Added bustImageCache() utility function in utils/imageUtils.ts
- Appends timestamp query parameter (?t=timestamp) to avatar URLs
- Skips cache-busting for local file://, data: URIs and placeholders
- Applied bustImageCache() to all avatar Image components:
  - Beneficiary detail screen (header, edit modal, lightbox)
  - Beneficiary list cards on dashboard
- Ensured loadBeneficiary() is called after avatar upload completes
- Added comprehensive unit tests for cache-busting logic

Backend already generates unique URLs with timestamps when uploading
to MinIO, but this ensures frontend always requests fresh images.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-29 11:22:49 -08:00

66 lines
2.2 KiB
TypeScript

import { bustImageCache } from '../imageUtils';
describe('bustImageCache', () => {
it('should add cache-busting parameter to HTTP URLs', () => {
const url = 'https://example.com/avatar.jpg';
const result = bustImageCache(url, 1234567890);
expect(result).toBe('https://example.com/avatar.jpg?t=1234567890');
});
it('should add cache-busting parameter to URLs with existing query params', () => {
const url = 'https://example.com/avatar.jpg?size=large';
const result = bustImageCache(url, 1234567890);
expect(result).toBe('https://example.com/avatar.jpg?size=large&t=1234567890');
});
it('should not add cache-busting to file:// URIs', () => {
const uri = 'file:///path/to/local/image.jpg';
const result = bustImageCache(uri);
expect(result).toBe(uri);
});
it('should not add cache-busting to data URIs', () => {
const dataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA';
const result = bustImageCache(dataUri);
expect(result).toBe(dataUri);
});
it('should not add cache-busting to placeholder images', () => {
const placeholderUrl = 'https://example.com/placeholder.png';
const result = bustImageCache(placeholderUrl);
expect(result).toBe(placeholderUrl);
});
it('should return null for null input', () => {
const result = bustImageCache(null);
expect(result).toBeNull();
});
it('should return null for undefined input', () => {
const result = bustImageCache(undefined);
expect(result).toBeNull();
});
it('should return null for empty string', () => {
const result = bustImageCache('');
expect(result).toBeNull();
});
it('should use current timestamp when not provided', () => {
const url = 'https://example.com/avatar.jpg';
const beforeTimestamp = Date.now();
const result = bustImageCache(url);
const afterTimestamp = Date.now();
// Extract timestamp from result
const match = result?.match(/t=(\d+)/);
expect(match).toBeTruthy();
if (match) {
const timestamp = parseInt(match[1], 10);
expect(timestamp).toBeGreaterThanOrEqual(beforeTimestamp);
expect(timestamp).toBeLessThanOrEqual(afterTimestamp);
}
});
});