forked from cartsnitch/app
feat: add utility functions with unit tests (#63)
feat: add utility functions with unit tests
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { formatCurrency } from '../formatCurrency';
|
||||
|
||||
describe('formatCurrency', () => {
|
||||
it('formats 0 cents as $0.00', () => {
|
||||
expect(formatCurrency(0)).toBe('$0.00');
|
||||
});
|
||||
|
||||
it('formats 199 cents as $1.99', () => {
|
||||
expect(formatCurrency(199)).toBe('$1.99');
|
||||
});
|
||||
|
||||
it('formats 10000 cents as $100.00', () => {
|
||||
expect(formatCurrency(10000)).toBe('$100.00');
|
||||
});
|
||||
|
||||
it('handles negative values', () => {
|
||||
expect(formatCurrency(-500)).toBe('-$5.00');
|
||||
});
|
||||
|
||||
it('handles large numbers', () => {
|
||||
expect(formatCurrency(99999999)).toBe('$999,999.99');
|
||||
});
|
||||
|
||||
it('supports custom locale', () => {
|
||||
expect(formatCurrency(1999, 'de-DE', 'EUR')).toContain('19,99');
|
||||
});
|
||||
|
||||
it('supports custom currency', () => {
|
||||
const result = formatCurrency(1000, 'en-US', 'EUR');
|
||||
expect(result).toContain('10.00');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { formatDate } from '../formatDate';
|
||||
|
||||
describe('formatDate', () => {
|
||||
describe('short style', () => {
|
||||
it('formats an ISO date string', () => {
|
||||
const result = formatDate('2024-03-15', 'short');
|
||||
expect(result).toMatch(/Mar 15, 2024/);
|
||||
});
|
||||
|
||||
it('formats a Date object', () => {
|
||||
const result = formatDate(new Date('2024-03-15'), 'short');
|
||||
expect(result).toMatch(/Mar 15, 2024/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('long style', () => {
|
||||
it('formats with weekday and full month name', () => {
|
||||
const result = formatDate('2024-03-15', 'long');
|
||||
expect(result).toMatch(/Friday/);
|
||||
expect(result).toMatch(/March/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('relative style', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('returns "just now" for very recent dates', () => {
|
||||
const now = new Date('2024-01-01T12:00:00Z');
|
||||
vi.setSystemTime(now);
|
||||
const result = formatDate(new Date('2024-01-01T11:59:59Z'), 'relative');
|
||||
expect(result).toBe('just now');
|
||||
});
|
||||
|
||||
it('returns minutes ago', () => {
|
||||
const now = new Date('2024-01-01T12:00:00Z');
|
||||
vi.setSystemTime(now);
|
||||
const result = formatDate(new Date('2024-01-01T11:45:00Z'), 'relative');
|
||||
expect(result).toBe('15m ago');
|
||||
});
|
||||
|
||||
it('returns hours ago', () => {
|
||||
const now = new Date('2024-01-01T12:00:00Z');
|
||||
vi.setSystemTime(now);
|
||||
const result = formatDate(new Date('2024-01-01T09:00:00Z'), 'relative');
|
||||
expect(result).toBe('3h ago');
|
||||
});
|
||||
|
||||
it('returns days ago', () => {
|
||||
const now = new Date('2024-01-05T12:00:00Z');
|
||||
vi.setSystemTime(now);
|
||||
const result = formatDate(new Date('2024-01-01T12:00:00Z'), 'relative');
|
||||
expect(result).toBe('4d ago');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { getStore, getStoreName, STORE_SLUGS } from '../storeSlugs';
|
||||
|
||||
describe('storeSlugs', () => {
|
||||
describe('STORE_SLUGS constant', () => {
|
||||
it('contains meijer, kroger, and target', () => {
|
||||
expect(STORE_SLUGS).toHaveProperty('meijer');
|
||||
expect(STORE_SLUGS).toHaveProperty('kroger');
|
||||
expect(STORE_SLUGS).toHaveProperty('target');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStore', () => {
|
||||
it('returns store data for known slug', () => {
|
||||
const store = getStore('meijer');
|
||||
expect(store).toEqual({
|
||||
name: 'Meijer',
|
||||
color: '#e31837',
|
||||
icon: '/icons/stores/meijer.svg',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null for unknown slug', () => {
|
||||
expect(getStore('unknown-store')).toBeNull();
|
||||
});
|
||||
|
||||
it('is case insensitive', () => {
|
||||
expect(getStore('KROGER')).toBeTruthy();
|
||||
expect(getStore('Target')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStoreName', () => {
|
||||
it('returns store name for known slug', () => {
|
||||
expect(getStoreName('kroger')).toBe('Kroger');
|
||||
});
|
||||
|
||||
it('returns raw slug for unknown store', () => {
|
||||
expect(getStoreName('unknown-store')).toBe('unknown-store');
|
||||
});
|
||||
|
||||
it('is case insensitive', () => {
|
||||
expect(getStoreName('TARGET')).toBe('Target');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
export function formatCurrency(
|
||||
cents: number,
|
||||
locale = 'en-US',
|
||||
currency = 'USD'
|
||||
): string {
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency,
|
||||
}).format(cents / 100);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
export function formatDate(
|
||||
date: string | Date,
|
||||
style: 'short' | 'long' | 'relative' = 'short'
|
||||
): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
|
||||
if (style === 'short') {
|
||||
return d.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
if (style === 'long') {
|
||||
return d.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
// relative
|
||||
const diff = Date.now() - d.getTime();
|
||||
const seconds = Math.floor(diff / 1000);
|
||||
if (seconds < 60) return 'just now';
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
const days = Math.floor(hours / 24);
|
||||
return `${days}d ago`;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export const STORE_SLUGS: Record<string, { name: string; color: string; icon: string }> = {
|
||||
meijer: { name: 'Meijer', color: '#e31837', icon: '/icons/stores/meijer.svg' },
|
||||
kroger: { name: 'Kroger', color: '#0033a0', icon: '/icons/stores/kroger.svg' },
|
||||
target: { name: 'Target', color: '#cc0000', icon: '/icons/stores/target.svg' },
|
||||
};
|
||||
|
||||
export function getStore(slug: string) {
|
||||
return STORE_SLUGS[slug.toLowerCase()] ?? null;
|
||||
}
|
||||
|
||||
export function getStoreName(slug: string): string {
|
||||
return getStore(slug)?.name ?? slug;
|
||||
}
|
||||
Reference in New Issue
Block a user