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>
66 lines
2.2 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|