/**
* @fileoverview Tests for MetricValue value object
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { MetricValue } from '../metric-value.js';
describe('MetricValue', () => {
let baseDate: Date;
beforeEach(() => {
baseDate = new Date('2024-01-01T00:00:00Z');
});
describe('create', () => {
it('should create a metric value with all parameters', () => {
const metric = MetricValue.create(42.5, '%', '42.5%', baseDate);
expect(metric.value).toBe(42.5);
expect(metric.unit).toBe('%');
expect(metric.displayValue).toBe('42.5%');
expect(metric.measuredAt).toEqual(baseDate);
});
it('should create a metric value with default display value', () => {
const metric = MetricValue.create(100, 'ms');
expect(metric.value).toBe(100);
expect(metric.unit).toBe('ms');
expect(metric.displayValue).toBe('100ms');
});
it('should create a metric value with current timestamp by default', () => {
const before = new Date();
const metric = MetricValue.create(50, 'count');
const after = new Date();
expect(metric.measuredAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
expect(metric.measuredAt.getTime()).toBeLessThanOrEqual(after.getTime());
});
it('should throw error for non-finite values', () => {
expect(() => MetricValue.create(Infinity, '%')).toThrow(
'Metric value must be a finite number'
);
expect(() => MetricValue.create(NaN, '%')).toThrow('Metric value must be a finite number');
});
it('should throw error for empty unit', () => {
expect(() => MetricValue.create(50, '')).toThrow('Unit cannot be empty');
});
it('should handle zero values', () => {
const metric = MetricValue.create(0, 'count');
expect(metric.value).toBe(0);
});
it('should handle negative values', () => {
const metric = MetricValue.create(-10, 'delta');
expect(metric.value).toBe(-10);
});
it('should use custom display value when provided', () => {
const metric = MetricValue.create(12, 'points', '12/20');
expect(metric.displayValue).toBe('12/20');
});
});
describe('createPercentage', () => {
it('should create a percentage metric with default decimal places', () => {
const metric = MetricValue.createPercentage(85.567);
expect(metric.value).toBe(85.567);
expect(metric.unit).toBe('%');
expect(metric.displayValue).toBe('85.6%');
expect(metric.isPercentage).toBe(true);
});
it('should create a percentage metric with custom decimal places', () => {
const metric = MetricValue.createPercentage(85.567, 2);
expect(metric.displayValue).toBe('85.57%');
});
it('should handle zero decimal places', () => {
const metric = MetricValue.createPercentage(85.567, 0);
expect(metric.displayValue).toBe('86%');
});
it('should handle 0%', () => {
const metric = MetricValue.createPercentage(0);
expect(metric.value).toBe(0);
expect(metric.displayValue).toBe('0.0%');
});
it('should handle 100%', () => {
const metric = MetricValue.createPercentage(100);
expect(metric.value).toBe(100);
expect(metric.displayValue).toBe('100.0%');
});
});
describe('createUnknown', () => {
it('should create an unknown metric value', () => {
const metric = MetricValue.createUnknown('count');
expect(metric.value).toBe(0);
expect(metric.unit).toBe('count');
expect(metric.displayValue).toBe('N/A');
expect(metric.isUnknown).toBe(true);
});
it('should handle different units', () => {
const metric = MetricValue.createUnknown('%');
expect(metric.unit).toBe('%');
expect(metric.isUnknown).toBe(true);
});
});
describe('getters', () => {
it('should provide access to all properties', () => {
const metric = MetricValue.create(42, 'count', 'Custom: 42', baseDate);
expect(metric.value).toBe(42);
expect(metric.unit).toBe('count');
expect(metric.displayValue).toBe('Custom: 42');
expect(metric.measuredAt).toEqual(baseDate);
});
it('should correctly identify percentage metrics', () => {
const percentage = MetricValue.createPercentage(50);
const other = MetricValue.create(50, 'ratio');
expect(percentage.isPercentage).toBe(true);
expect(other.isPercentage).toBe(false);
});
it('should correctly identify unknown metrics', () => {
const unknown = MetricValue.createUnknown('count');
const known = MetricValue.create(0, 'count');
expect(unknown.isUnknown).toBe(true);
expect(known.isUnknown).toBe(false);
});
});
describe('difference', () => {
it('should calculate difference between metrics with same unit', () => {
const metric1 = MetricValue.create(100, 'count');
const metric2 = MetricValue.create(75, 'count');
expect(metric1.difference(metric2)).toBe(25);
expect(metric2.difference(metric1)).toBe(-25);
});
it('should throw error for metrics with different units', () => {
const metric1 = MetricValue.create(100, 'count');
const metric2 = MetricValue.create(75, '%');
expect(() => metric1.difference(metric2)).toThrow(
'Cannot calculate difference between metrics with different units'
);
});
it('should handle zero differences', () => {
const metric1 = MetricValue.create(50, 'count');
const metric2 = MetricValue.create(50, 'count');
expect(metric1.difference(metric2)).toBe(0);
});
it('should handle negative values', () => {
const metric1 = MetricValue.create(-10, 'delta');
const metric2 = MetricValue.create(-20, 'delta');
expect(metric1.difference(metric2)).toBe(10);
});
});
describe('percentageChange', () => {
it('should calculate positive percentage change', () => {
const previous = MetricValue.create(100, 'count');
const current = MetricValue.create(150, 'count');
expect(current.percentageChange(previous)).toBe(50);
});
it('should calculate negative percentage change', () => {
const previous = MetricValue.create(100, 'count');
const current = MetricValue.create(75, 'count');
expect(current.percentageChange(previous)).toBe(-25);
});
it('should handle zero previous value', () => {
const previous = MetricValue.create(0, 'count');
const current = MetricValue.create(100, 'count');
expect(current.percentageChange(previous)).toBe(100);
});
it('should return 0 when current is 0 and previous is 0', () => {
const previous = MetricValue.create(0, 'count');
const current = MetricValue.create(0, 'count');
expect(current.percentageChange(previous)).toBe(0);
});
it('should throw error for metrics with different units', () => {
const previous = MetricValue.create(100, 'count');
const current = MetricValue.create(150, '%');
expect(() => current.percentageChange(previous)).toThrow(
'Cannot calculate percentage change between metrics with different units'
);
});
it('should handle decimal changes', () => {
const previous = MetricValue.create(10, 'count');
const current = MetricValue.create(12.5, 'count');
expect(current.percentageChange(previous)).toBe(25);
});
it('should handle very small values', () => {
const previous = MetricValue.create(0.01, 'ratio');
const current = MetricValue.create(0.02, 'ratio');
expect(current.percentageChange(previous)).toBe(100);
});
});
describe('toString', () => {
it('should return the display value', () => {
const metric = MetricValue.create(42, 'count', 'Custom Display');
expect(metric.toString()).toBe('Custom Display');
});
it('should return default formatted value', () => {
const metric = MetricValue.create(100, 'ms');
expect(metric.toString()).toBe('100ms');
});
it('should return N/A for unknown values', () => {
const metric = MetricValue.createUnknown('count');
expect(metric.toString()).toBe('N/A');
});
});
describe('equals', () => {
it('should return true for metrics with same properties', () => {
const date = new Date();
const metric1 = MetricValue.create(42, 'count', '42 items', date);
const metric2 = MetricValue.create(42, 'count', '42 items', date);
expect(metric1.equals(metric2)).toBe(true);
});
it('should return false for metrics with different values', () => {
const metric1 = MetricValue.create(42, 'count');
const metric2 = MetricValue.create(43, 'count');
expect(metric1.equals(metric2)).toBe(false);
});
it('should return false for metrics with different units', () => {
const metric1 = MetricValue.create(42, 'count');
const metric2 = MetricValue.create(42, '%');
expect(metric1.equals(metric2)).toBe(false);
});
it('should return false for metrics with different display values', () => {
const metric1 = MetricValue.create(42, 'count', 'Display 1');
const metric2 = MetricValue.create(42, 'count', 'Display 2');
expect(metric1.equals(metric2)).toBe(false);
});
it('should return false for metrics with different timestamps', () => {
const date1 = new Date('2024-01-01');
const date2 = new Date('2024-01-02');
const metric1 = MetricValue.create(42, 'count', undefined, date1);
const metric2 = MetricValue.create(42, 'count', undefined, date2);
expect(metric1.equals(metric2)).toBe(false);
});
});
describe('immutability', () => {
it('should be immutable after creation', () => {
const metric = MetricValue.create(42, 'count');
// Properties should be readonly
// @ts-expect-error - Testing immutability
expect(() => {
metric.value = 50;
}).toThrow();
// @ts-expect-error - Testing immutability
expect(() => {
metric.unit = '%';
}).toThrow();
});
});
});