← back to Handbag Auth Nextjs

auction-viewer/tests/unit/calculations.test.js

266 lines

/**
 * Unit Tests - Savings Calculations and Business Logic
 */

describe('Savings Calculations', () => {

  describe('Percentage Savings', () => {
    const calculateSavingsPercent = (estimateLow, currentPrice) => {
      if (estimateLow > 0 && currentPrice > 0) {
        return ((estimateLow - currentPrice) / estimateLow) * 100;
      }
      return 0;
    };

    test('should calculate positive savings correctly', () => {
      const result = calculateSavingsPercent(10000, 8000);
      expect(result).toBe(20);
    });

    test('should calculate high savings correctly', () => {
      const result = calculateSavingsPercent(20000, 10000);
      expect(result).toBe(50);
    });

    test('should handle zero current price', () => {
      const result = calculateSavingsPercent(10000, 0);
      expect(result).toBe(0);
    });

    test('should handle zero estimate', () => {
      const result = calculateSavingsPercent(0, 5000);
      expect(result).toBe(0);
    });

    test('should handle negative savings (price above estimate)', () => {
      const result = calculateSavingsPercent(8000, 10000);
      expect(result).toBe(-25);
    });

    test('should handle small savings accurately', () => {
      const result = calculateSavingsPercent(1000, 999);
      expect(result).toBeCloseTo(0.1, 1);
    });

    test('should handle large numbers', () => {
      const result = calculateSavingsPercent(100000, 75000);
      expect(result).toBe(25);
    });
  });

  describe('Dollar Savings', () => {
    const calculateSavingsAmount = (estimateLow, currentPrice) => {
      if (estimateLow > 0 && currentPrice > 0) {
        return estimateLow - currentPrice;
      }
      return 0;
    };

    test('should calculate dollar savings correctly', () => {
      const result = calculateSavingsAmount(10000, 8000);
      expect(result).toBe(2000);
    });

    test('should handle zero prices', () => {
      const result = calculateSavingsAmount(0, 0);
      expect(result).toBe(0);
    });

    test('should handle negative savings', () => {
      const result = calculateSavingsAmount(8000, 10000);
      expect(result).toBe(-2000);
    });

    test('should handle large amounts', () => {
      const result = calculateSavingsAmount(50000, 30000);
      expect(result).toBe(20000);
    });
  });

  describe('Value Ranking', () => {
    const rankByValue = (auctions, sortBy = 'percent') => {
      return auctions
        .filter(a => a.current_price > 0 && a.estimate_low > 0)
        .map(a => ({
          ...a,
          savings_percent: ((a.estimate_low - a.current_price) / a.estimate_low) * 100,
          savings_amount: a.estimate_low - a.current_price
        }))
        .sort((a, b) => {
          if (sortBy === 'percent') {
            return b.savings_percent - a.savings_percent;
          }
          return b.savings_amount - a.savings_amount;
        });
    };

    const testAuctions = [
      { auction_id: '1', estimate_low: 10000, current_price: 5000 }, // 50% / $5000
      { auction_id: '2', estimate_low: 20000, current_price: 15000 }, // 25% / $5000
      { auction_id: '3', estimate_low: 5000, current_price: 1000 }, // 80% / $4000
    ];

    test('should rank by percentage correctly', () => {
      const ranked = rankByValue(testAuctions, 'percent');

      expect(ranked[0].auction_id).toBe('3'); // 80%
      expect(ranked[1].auction_id).toBe('1'); // 50%
      expect(ranked[2].auction_id).toBe('2'); // 25%
    });

    test('should rank by dollar amount correctly', () => {
      const ranked = rankByValue(testAuctions, 'dollar');

      // Both auction 1 and 2 have $5000 savings, auction 3 has $4000
      expect(['1', '2']).toContain(ranked[0].auction_id);
      expect(ranked[2].auction_id).toBe('3');
    });

    test('should filter out invalid prices', () => {
      const withInvalid = [
        ...testAuctions,
        { auction_id: '4', estimate_low: 0, current_price: 1000 },
        { auction_id: '5', estimate_low: 1000, current_price: 0 }
      ];

      const ranked = rankByValue(withInvalid);
      expect(ranked.length).toBe(3); // Should exclude invalid items
    });

    test('should handle empty array', () => {
      const ranked = rankByValue([]);
      expect(ranked).toEqual([]);
    });
  });

  describe('Price Validation', () => {
    const isValidPrice = (price) => {
      return typeof price === 'number' && price > 0 && isFinite(price);
    };

    test('should accept valid positive numbers', () => {
      expect(isValidPrice(100)).toBe(true);
      expect(isValidPrice(0.01)).toBe(true);
      expect(isValidPrice(999999)).toBe(true);
    });

    test('should reject zero and negative numbers', () => {
      expect(isValidPrice(0)).toBe(false);
      expect(isValidPrice(-100)).toBe(false);
    });

    test('should reject non-numbers', () => {
      expect(isValidPrice('100')).toBe(false);
      expect(isValidPrice(null)).toBe(false);
      expect(isValidPrice(undefined)).toBe(false);
      expect(isValidPrice(NaN)).toBe(false);
    });

    test('should reject infinity', () => {
      expect(isValidPrice(Infinity)).toBe(false);
      expect(isValidPrice(-Infinity)).toBe(false);
    });
  });

  describe('Data Formatting', () => {
    const formatCurrency = (amount) => {
      if (typeof amount !== 'number' || !isFinite(amount)) {
        return '$0.00';
      }
      return `$${amount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
    };

    const formatPercent = (percent) => {
      if (typeof percent !== 'number' || !isFinite(percent)) {
        return '0%';
      }
      return `${percent.toFixed(1)}%`;
    };

    test('should format currency correctly', () => {
      expect(formatCurrency(1000)).toBe('$1,000.00');
      expect(formatCurrency(1234567.89)).toBe('$1,234,567.89');
      expect(formatCurrency(0.99)).toBe('$0.99');
    });

    test('should format percentage correctly', () => {
      expect(formatPercent(25)).toBe('25.0%');
      expect(formatPercent(33.333)).toBe('33.3%');
      expect(formatPercent(0.1)).toBe('0.1%');
    });

    test('should handle invalid values gracefully', () => {
      expect(formatCurrency(null)).toBe('$0.00');
      expect(formatCurrency(NaN)).toBe('$0.00');
      expect(formatPercent(undefined)).toBe('0%');
      expect(formatPercent(Infinity)).toBe('0%');
    });
  });

  describe('Statistics Calculations', () => {
    const calculateStats = (auctions) => {
      const validAuctions = auctions.filter(a => a.current_price > 0);

      if (validAuctions.length === 0) {
        return {
          total: 0,
          avgPrice: 0,
          minPrice: 0,
          maxPrice: 0,
          totalValue: 0
        };
      }

      const prices = validAuctions.map(a => a.current_price);

      return {
        total: auctions.length,
        avgPrice: prices.reduce((sum, p) => sum + p, 0) / prices.length,
        minPrice: Math.min(...prices),
        maxPrice: Math.max(...prices),
        totalValue: prices.reduce((sum, p) => sum + p, 0)
      };
    };

    const testData = [
      { current_price: 1000 },
      { current_price: 2000 },
      { current_price: 3000 },
      { current_price: 4000 }
    ];

    test('should calculate average correctly', () => {
      const stats = calculateStats(testData);
      expect(stats.avgPrice).toBe(2500);
    });

    test('should find min and max', () => {
      const stats = calculateStats(testData);
      expect(stats.minPrice).toBe(1000);
      expect(stats.maxPrice).toBe(4000);
    });

    test('should calculate total value', () => {
      const stats = calculateStats(testData);
      expect(stats.totalValue).toBe(10000);
    });

    test('should handle empty array', () => {
      const stats = calculateStats([]);
      expect(stats.total).toBe(0);
      expect(stats.avgPrice).toBe(0);
    });

    test('should filter out invalid prices', () => {
      const withInvalid = [
        ...testData,
        { current_price: 0 },
        { current_price: -100 }
      ];

      const stats = calculateStats(withInvalid);
      expect(stats.avgPrice).toBe(2500); // Should only count valid prices
    });
  });
});