feat: test coverage for utils
This commit is contained in:
176
src/shared/lib/utils/clampNumber/clampNumber.test.ts
Normal file
176
src/shared/lib/utils/clampNumber/clampNumber.test.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* Tests for clampNumber utility
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
test,
|
||||||
|
} from 'vitest';
|
||||||
|
import { clampNumber } from './clampNumber';
|
||||||
|
|
||||||
|
describe('clampNumber', () => {
|
||||||
|
describe('basic functionality', () => {
|
||||||
|
test('should return value when within range', () => {
|
||||||
|
expect(clampNumber(5, 0, 10)).toBe(5);
|
||||||
|
expect(clampNumber(0.5, 0, 1)).toBe(0.5);
|
||||||
|
expect(clampNumber(-3, -10, 10)).toBe(-3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clamp value to minimum', () => {
|
||||||
|
expect(clampNumber(-5, 0, 10)).toBe(0);
|
||||||
|
expect(clampNumber(-100, -50, 100)).toBe(-50);
|
||||||
|
expect(clampNumber(0, 1, 10)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clamp value to maximum', () => {
|
||||||
|
expect(clampNumber(15, 0, 10)).toBe(10);
|
||||||
|
expect(clampNumber(150, -50, 100)).toBe(100);
|
||||||
|
expect(clampNumber(100, 1, 50)).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle boundary values', () => {
|
||||||
|
expect(clampNumber(0, 0, 10)).toBe(0);
|
||||||
|
expect(clampNumber(10, 0, 10)).toBe(10);
|
||||||
|
expect(clampNumber(-5, -5, 5)).toBe(-5);
|
||||||
|
expect(clampNumber(5, -5, 5)).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('negative ranges', () => {
|
||||||
|
test('should handle fully negative ranges', () => {
|
||||||
|
expect(clampNumber(-5, -10, -1)).toBe(-5);
|
||||||
|
expect(clampNumber(-15, -10, -1)).toBe(-10);
|
||||||
|
expect(clampNumber(-0.5, -10, -1)).toBe(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle ranges spanning zero', () => {
|
||||||
|
expect(clampNumber(0, -10, 10)).toBe(0);
|
||||||
|
expect(clampNumber(-5, -10, 10)).toBe(-5);
|
||||||
|
expect(clampNumber(5, -10, 10)).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('floating-point numbers', () => {
|
||||||
|
test('should clamp floating-point values correctly', () => {
|
||||||
|
expect(clampNumber(0.75, 0, 1)).toBe(0.75);
|
||||||
|
expect(clampNumber(1.5, 0, 1)).toBe(1);
|
||||||
|
expect(clampNumber(-0.25, 0, 1)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle very small decimals', () => {
|
||||||
|
expect(clampNumber(0.001, 0, 0.01)).toBe(0.001);
|
||||||
|
expect(clampNumber(0.1, 0, 0.01)).toBe(0.01);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle large floating-point numbers', () => {
|
||||||
|
expect(clampNumber(123.456, 100, 200)).toBe(123.456);
|
||||||
|
expect(clampNumber(99.999, 100, 200)).toBe(100);
|
||||||
|
expect(clampNumber(200.001, 100, 200)).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('edge cases', () => {
|
||||||
|
test('should handle when min equals max', () => {
|
||||||
|
expect(clampNumber(5, 10, 10)).toBe(10);
|
||||||
|
expect(clampNumber(10, 10, 10)).toBe(10);
|
||||||
|
expect(clampNumber(15, 10, 10)).toBe(10);
|
||||||
|
expect(clampNumber(0, 0, 0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle zero values', () => {
|
||||||
|
expect(clampNumber(0, 0, 10)).toBe(0);
|
||||||
|
expect(clampNumber(0, -10, 10)).toBe(0);
|
||||||
|
expect(clampNumber(5, 0, 0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle reversed min/max (min > max)', () => {
|
||||||
|
// When min > max, Math.max/Math.min will still produce a result
|
||||||
|
// but it's logically incorrect - we test the actual behavior
|
||||||
|
// Math.min(Math.max(5, 10), 0) = Math.min(10, 0) = 0
|
||||||
|
expect(clampNumber(5, 10, 0)).toBe(0);
|
||||||
|
expect(clampNumber(15, 10, 0)).toBe(0);
|
||||||
|
expect(clampNumber(-5, 10, 0)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('special number values', () => {
|
||||||
|
test('should handle Infinity', () => {
|
||||||
|
expect(clampNumber(Infinity, 0, 10)).toBe(10);
|
||||||
|
expect(clampNumber(-Infinity, 0, 10)).toBe(0);
|
||||||
|
expect(clampNumber(5, -Infinity, Infinity)).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle NaN', () => {
|
||||||
|
expect(clampNumber(NaN, 0, 10)).toBeNaN();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('real-world scenarios', () => {
|
||||||
|
test('should clamp font size values', () => {
|
||||||
|
// Typical font size range: 8px to 72px
|
||||||
|
expect(clampNumber(16, 8, 72)).toBe(16);
|
||||||
|
expect(clampNumber(4, 8, 72)).toBe(8);
|
||||||
|
expect(clampNumber(100, 8, 72)).toBe(72);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clamp slider values', () => {
|
||||||
|
// Slider range: 0 to 100
|
||||||
|
expect(clampNumber(50, 0, 100)).toBe(50);
|
||||||
|
expect(clampNumber(-10, 0, 100)).toBe(0);
|
||||||
|
expect(clampNumber(150, 0, 100)).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clamp opacity values', () => {
|
||||||
|
// Opacity range: 0 to 1
|
||||||
|
expect(clampNumber(0.5, 0, 1)).toBe(0.5);
|
||||||
|
expect(clampNumber(-0.2, 0, 1)).toBe(0);
|
||||||
|
expect(clampNumber(1.2, 0, 1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clamp percentage values', () => {
|
||||||
|
// Percentage range: 0 to 100
|
||||||
|
expect(clampNumber(75, 0, 100)).toBe(75);
|
||||||
|
expect(clampNumber(-5, 0, 100)).toBe(0);
|
||||||
|
expect(clampNumber(105, 0, 100)).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clamp coordinate values', () => {
|
||||||
|
// Canvas coordinates: 0 to 800 width, 0 to 600 height
|
||||||
|
expect(clampNumber(400, 0, 800)).toBe(400);
|
||||||
|
expect(clampNumber(-50, 0, 800)).toBe(0);
|
||||||
|
expect(clampNumber(900, 0, 800)).toBe(800);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clamp font weight values', () => {
|
||||||
|
// Font weight range: 100 to 900 (in increments of 100)
|
||||||
|
expect(clampNumber(400, 100, 900)).toBe(400);
|
||||||
|
expect(clampNumber(50, 100, 900)).toBe(100);
|
||||||
|
expect(clampNumber(950, 100, 900)).toBe(900);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clamp line height values', () => {
|
||||||
|
// Line height range: 0.5 to 3.0
|
||||||
|
expect(clampNumber(1.5, 0.5, 3.0)).toBe(1.5);
|
||||||
|
expect(clampNumber(0.3, 0.5, 3.0)).toBe(0.5);
|
||||||
|
expect(clampNumber(4.0, 0.5, 3.0)).toBe(3.0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('numeric constraints', () => {
|
||||||
|
test('should handle very large numbers', () => {
|
||||||
|
expect(clampNumber(Number.MAX_VALUE, 0, 100)).toBe(100);
|
||||||
|
expect(clampNumber(Number.MIN_VALUE, -10, 10)).toBe(Number.MIN_VALUE);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle negative infinity boundaries', () => {
|
||||||
|
expect(clampNumber(5, -Infinity, 10)).toBe(5);
|
||||||
|
expect(clampNumber(-1000, -Infinity, 10)).toBe(-1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle positive infinity boundaries', () => {
|
||||||
|
expect(clampNumber(5, 0, Infinity)).toBe(5);
|
||||||
|
expect(clampNumber(1000, 0, Infinity)).toBe(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
188
src/shared/lib/utils/getDecimalPlaces/getDecimalPlaces.test.ts
Normal file
188
src/shared/lib/utils/getDecimalPlaces/getDecimalPlaces.test.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* Tests for getDecimalPlaces utility
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
test,
|
||||||
|
} from 'vitest';
|
||||||
|
import { getDecimalPlaces } from './getDecimalPlaces';
|
||||||
|
|
||||||
|
describe('getDecimalPlaces', () => {
|
||||||
|
describe('basic functionality', () => {
|
||||||
|
test('should return 0 for integers', () => {
|
||||||
|
expect(getDecimalPlaces(0)).toBe(0);
|
||||||
|
expect(getDecimalPlaces(1)).toBe(0);
|
||||||
|
expect(getDecimalPlaces(42)).toBe(0);
|
||||||
|
expect(getDecimalPlaces(-7)).toBe(0);
|
||||||
|
expect(getDecimalPlaces(1000)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct decimal places for decimals', () => {
|
||||||
|
expect(getDecimalPlaces(0.1)).toBe(1);
|
||||||
|
expect(getDecimalPlaces(0.5)).toBe(1);
|
||||||
|
expect(getDecimalPlaces(0.01)).toBe(2);
|
||||||
|
expect(getDecimalPlaces(0.05)).toBe(2);
|
||||||
|
expect(getDecimalPlaces(0.001)).toBe(3);
|
||||||
|
expect(getDecimalPlaces(0.123)).toBe(3);
|
||||||
|
expect(getDecimalPlaces(0.123456)).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle negative decimal numbers', () => {
|
||||||
|
expect(getDecimalPlaces(-0.1)).toBe(1);
|
||||||
|
expect(getDecimalPlaces(-0.05)).toBe(2);
|
||||||
|
expect(getDecimalPlaces(-1.5)).toBe(1);
|
||||||
|
expect(getDecimalPlaces(-99.99)).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('whole numbers with decimal part', () => {
|
||||||
|
test('should handle numbers with integer and decimal parts', () => {
|
||||||
|
expect(getDecimalPlaces(1.5)).toBe(1);
|
||||||
|
expect(getDecimalPlaces(10.25)).toBe(2);
|
||||||
|
expect(getDecimalPlaces(100.125)).toBe(3);
|
||||||
|
expect(getDecimalPlaces(1234.5678)).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle trailing zeros correctly', () => {
|
||||||
|
// Note: JavaScript string representation drops trailing zeros
|
||||||
|
expect(getDecimalPlaces(1.5)).toBe(1);
|
||||||
|
expect(getDecimalPlaces(1.50)).toBe(1); // 1.50 becomes "1.5" in string
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('edge cases', () => {
|
||||||
|
test('should handle zero', () => {
|
||||||
|
expect(getDecimalPlaces(0)).toBe(0);
|
||||||
|
expect(getDecimalPlaces(0.0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle very small decimals', () => {
|
||||||
|
expect(getDecimalPlaces(0.0001)).toBe(4);
|
||||||
|
expect(getDecimalPlaces(0.00001)).toBe(5);
|
||||||
|
expect(getDecimalPlaces(0.000001)).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle very large numbers', () => {
|
||||||
|
expect(getDecimalPlaces(123456789.123)).toBe(3);
|
||||||
|
expect(getDecimalPlaces(999999.9999)).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle negative whole numbers', () => {
|
||||||
|
expect(getDecimalPlaces(-1)).toBe(0);
|
||||||
|
expect(getDecimalPlaces(-100)).toBe(0);
|
||||||
|
expect(getDecimalPlaces(-9999)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('special number values', () => {
|
||||||
|
test('should handle Infinity', () => {
|
||||||
|
expect(getDecimalPlaces(Infinity)).toBe(0);
|
||||||
|
expect(getDecimalPlaces(-Infinity)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle NaN', () => {
|
||||||
|
expect(getDecimalPlaces(NaN)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scientific notation', () => {
|
||||||
|
test('should handle numbers in scientific notation', () => {
|
||||||
|
// Very small numbers may be represented in scientific notation
|
||||||
|
const tiny = 1e-10;
|
||||||
|
const result = getDecimalPlaces(tiny);
|
||||||
|
// The result depends on how JS represents this as a string
|
||||||
|
expect(typeof result).toBe('number');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle large scientific notation numbers', () => {
|
||||||
|
const large = 1.23e5; // 123000
|
||||||
|
expect(getDecimalPlaces(large)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('real-world scenarios', () => {
|
||||||
|
test('should handle currency values (2 decimal places)', () => {
|
||||||
|
expect(getDecimalPlaces(0.01)).toBe(2); // 1 cent
|
||||||
|
expect(getDecimalPlaces(0.99)).toBe(2); // 99 cents
|
||||||
|
// Note: JavaScript string representation drops trailing zeros
|
||||||
|
// 10.50 becomes "10.5" in string, so returns 1 decimal place
|
||||||
|
expect(getDecimalPlaces(10.50)).toBe(1); // $10.50
|
||||||
|
expect(getDecimalPlaces(999.99)).toBe(2); // $999.99
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle measurement values', () => {
|
||||||
|
expect(getDecimalPlaces(12.5)).toBe(1); // 12.5 mm
|
||||||
|
expect(getDecimalPlaces(12.34)).toBe(2); // 12.34 cm
|
||||||
|
expect(getDecimalPlaces(12.345)).toBe(3); // 12.345 m
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle step values for sliders', () => {
|
||||||
|
expect(getDecimalPlaces(0.1)).toBe(1); // Fine adjustment
|
||||||
|
expect(getDecimalPlaces(0.25)).toBe(2); // Quarter steps
|
||||||
|
expect(getDecimalPlaces(0.5)).toBe(1); // Half steps
|
||||||
|
expect(getDecimalPlaces(1)).toBe(0); // Whole steps
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle font size increments', () => {
|
||||||
|
expect(getDecimalPlaces(0.5)).toBe(1); // Half point increments
|
||||||
|
expect(getDecimalPlaces(1)).toBe(0); // Whole point increments
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle opacity values', () => {
|
||||||
|
expect(getDecimalPlaces(0.1)).toBe(1); // 10% increments
|
||||||
|
expect(getDecimalPlaces(0.05)).toBe(2); // 5% increments
|
||||||
|
expect(getDecimalPlaces(0.01)).toBe(2); // 1% increments
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle percentage values', () => {
|
||||||
|
expect(getDecimalPlaces(0.5)).toBe(1); // 0.5%
|
||||||
|
expect(getDecimalPlaces(12.5)).toBe(1); // 12.5%
|
||||||
|
expect(getDecimalPlaces(33.33)).toBe(2); // 33.33%
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle coordinate precision', () => {
|
||||||
|
expect(getDecimalPlaces(12.3456789)).toBe(7); // High precision GPS
|
||||||
|
expect(getDecimalPlaces(100.5)).toBe(1); // Low precision coordinates
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle time values', () => {
|
||||||
|
expect(getDecimalPlaces(0.1)).toBe(1); // 100ms
|
||||||
|
expect(getDecimalPlaces(0.01)).toBe(2); // 10ms
|
||||||
|
expect(getDecimalPlaces(0.001)).toBe(3); // 1ms
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('common step values', () => {
|
||||||
|
test('should correctly identify precision of common step values', () => {
|
||||||
|
expect(getDecimalPlaces(0.05)).toBe(2); // Very fine steps
|
||||||
|
expect(getDecimalPlaces(0.1)).toBe(1); // Fine steps
|
||||||
|
expect(getDecimalPlaces(0.25)).toBe(2); // Quarter steps
|
||||||
|
expect(getDecimalPlaces(0.5)).toBe(1); // Half steps
|
||||||
|
expect(getDecimalPlaces(1)).toBe(0); // Whole steps
|
||||||
|
expect(getDecimalPlaces(2)).toBe(0); // Even steps
|
||||||
|
expect(getDecimalPlaces(5)).toBe(0); // Five steps
|
||||||
|
expect(getDecimalPlaces(10)).toBe(0); // Ten steps
|
||||||
|
expect(getDecimalPlaces(25)).toBe(0); // Twenty-five steps
|
||||||
|
expect(getDecimalPlaces(50)).toBe(0); // Fifty steps
|
||||||
|
expect(getDecimalPlaces(100)).toBe(0); // Hundred steps
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('floating-point representation', () => {
|
||||||
|
test('should handle standard floating-point representation', () => {
|
||||||
|
expect(getDecimalPlaces(1.1)).toBe(1);
|
||||||
|
expect(getDecimalPlaces(1.2)).toBe(1);
|
||||||
|
expect(getDecimalPlaces(1.3)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle numbers that might have floating-point issues', () => {
|
||||||
|
// 0.1 + 0.2 = 0.30000000000000004 in JS
|
||||||
|
const sum = 0.1 + 0.2;
|
||||||
|
const places = getDecimalPlaces(sum);
|
||||||
|
// The function analyzes the string representation
|
||||||
|
expect(typeof places).toBe('number');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
/**
|
||||||
|
* Tests for roundToStepPrecision utility
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
test,
|
||||||
|
} from 'vitest';
|
||||||
|
import { roundToStepPrecision } from './roundToStepPrecision';
|
||||||
|
|
||||||
|
describe('roundToStepPrecision', () => {
|
||||||
|
describe('basic functionality', () => {
|
||||||
|
test('should return value unchanged for step=1', () => {
|
||||||
|
// step=1 has 0 decimal places, so it rounds to integers
|
||||||
|
expect(roundToStepPrecision(5, 1)).toBe(5);
|
||||||
|
expect(roundToStepPrecision(5.5, 1)).toBe(6); // rounds to nearest integer
|
||||||
|
expect(roundToStepPrecision(5.999, 1)).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should round to 1 decimal place for step=0.1', () => {
|
||||||
|
expect(roundToStepPrecision(1.23, 0.1)).toBeCloseTo(1.2);
|
||||||
|
expect(roundToStepPrecision(1.25, 0.1)).toBeCloseTo(1.3);
|
||||||
|
expect(roundToStepPrecision(1.29, 0.1)).toBeCloseTo(1.3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should round to 2 decimal places for step=0.01', () => {
|
||||||
|
expect(roundToStepPrecision(1.234, 0.01)).toBeCloseTo(1.23);
|
||||||
|
expect(roundToStepPrecision(1.235, 0.01)).toBeCloseTo(1.24);
|
||||||
|
expect(roundToStepPrecision(1.239, 0.01)).toBeCloseTo(1.24);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should round to 3 decimal places for step=0.001', () => {
|
||||||
|
expect(roundToStepPrecision(1.2345, 0.001)).toBeCloseTo(1.235);
|
||||||
|
expect(roundToStepPrecision(1.2344, 0.001)).toBeCloseTo(1.234);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('floating-point precision issues', () => {
|
||||||
|
test('should fix floating-point precision errors with step=0.05', () => {
|
||||||
|
// Known floating-point issue: 0.1 + 0.05 = 0.15000000000000002
|
||||||
|
const value = 0.1 + 0.05;
|
||||||
|
const result = roundToStepPrecision(value, 0.05);
|
||||||
|
expect(result).toBeCloseTo(0.15, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fix floating-point errors with repeated additions', () => {
|
||||||
|
// Simulate adding 0.05 multiple times
|
||||||
|
let value = 1;
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
value += 0.05;
|
||||||
|
}
|
||||||
|
// value should be 1.5 but might be 1.4999999999999998
|
||||||
|
const result = roundToStepPrecision(value, 0.05);
|
||||||
|
expect(result).toBeCloseTo(1.5, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fix floating-point errors with step=0.1', () => {
|
||||||
|
// Known floating-point issue: 0.1 + 0.2 = 0.30000000000000004
|
||||||
|
const value = 0.1 + 0.2;
|
||||||
|
const result = roundToStepPrecision(value, 0.1);
|
||||||
|
expect(result).toBeCloseTo(0.3, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fix floating-point errors with step=0.01', () => {
|
||||||
|
// Known floating-point issue: 0.01 + 0.02 = 0.029999999999999999
|
||||||
|
const value = 0.01 + 0.02;
|
||||||
|
const result = roundToStepPrecision(value, 0.01);
|
||||||
|
expect(result).toBeCloseTo(0.03, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fix floating-point errors with step=0.25', () => {
|
||||||
|
const value = 0.5 + 0.25;
|
||||||
|
const result = roundToStepPrecision(value, 0.25);
|
||||||
|
expect(result).toBeCloseTo(0.75, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle classic 0.1 + 0.2 problem', () => {
|
||||||
|
// Classic JavaScript floating-point issue
|
||||||
|
const value = 0.1 + 0.2;
|
||||||
|
// Without rounding: 0.30000000000000004
|
||||||
|
const result = roundToStepPrecision(value, 0.1);
|
||||||
|
expect(result).toBe(0.3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('edge cases', () => {
|
||||||
|
test('should return value unchanged when step <= 0', () => {
|
||||||
|
expect(roundToStepPrecision(5, 0)).toBe(5);
|
||||||
|
expect(roundToStepPrecision(5, -1)).toBe(5);
|
||||||
|
expect(roundToStepPrecision(5, -0.5)).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle zero value', () => {
|
||||||
|
expect(roundToStepPrecision(0, 0.1)).toBe(0);
|
||||||
|
expect(roundToStepPrecision(0, 0.01)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle negative values', () => {
|
||||||
|
expect(roundToStepPrecision(-1.234, 0.01)).toBeCloseTo(-1.23);
|
||||||
|
expect(roundToStepPrecision(-0.15, 0.05)).toBeCloseTo(-0.15);
|
||||||
|
expect(roundToStepPrecision(-5.5, 0.5)).toBeCloseTo(-5.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle very small step values', () => {
|
||||||
|
expect(roundToStepPrecision(1.1234, 0.0001)).toBeCloseTo(1.1234);
|
||||||
|
expect(roundToStepPrecision(1.12345, 0.0001)).toBeCloseTo(1.1235);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle very large values', () => {
|
||||||
|
expect(roundToStepPrecision(12345.6789, 0.01)).toBeCloseTo(12345.68);
|
||||||
|
expect(roundToStepPrecision(99999.9999, 0.001)).toBeCloseTo(100000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('special number values', () => {
|
||||||
|
test('should handle Infinity', () => {
|
||||||
|
expect(roundToStepPrecision(Infinity, 0.1)).toBe(Infinity);
|
||||||
|
expect(roundToStepPrecision(-Infinity, 0.1)).toBe(-Infinity);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle NaN', () => {
|
||||||
|
expect(roundToStepPrecision(NaN, 0.1)).toBeNaN();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle step=Infinity', () => {
|
||||||
|
// getDecimalPlaces(Infinity) returns 0, so this rounds to 0 decimal places (integer)
|
||||||
|
const result = roundToStepPrecision(1.234, Infinity);
|
||||||
|
expect(result).toBeCloseTo(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('real-world scenarios', () => {
|
||||||
|
test('should handle currency calculations with step=0.01', () => {
|
||||||
|
// Add items with tax that might have floating-point errors
|
||||||
|
const subtotal = 10.99 + 5.99 + 2.99;
|
||||||
|
const rounded = roundToStepPrecision(subtotal, 0.01);
|
||||||
|
expect(rounded).toBeCloseTo(19.97, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle slider values with step=0.1', () => {
|
||||||
|
// Slider value after multiple increments
|
||||||
|
let sliderValue = 0;
|
||||||
|
for (let i = 0; i < 15; i++) {
|
||||||
|
sliderValue += 0.1;
|
||||||
|
}
|
||||||
|
const rounded = roundToStepPrecision(sliderValue, 0.1);
|
||||||
|
expect(rounded).toBeCloseTo(1.5, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle font size adjustments with step=0.5', () => {
|
||||||
|
// Font size adjustments
|
||||||
|
let fontSize = 12;
|
||||||
|
fontSize += 0.5; // 12.5
|
||||||
|
fontSize += 0.5; // 13.0
|
||||||
|
const rounded = roundToStepPrecision(fontSize, 0.5);
|
||||||
|
expect(rounded).toBeCloseTo(13, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle opacity values with step=0.05', () => {
|
||||||
|
// Opacity from 0 to 1 in 5% increments
|
||||||
|
let opacity = 0;
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
opacity += 0.05;
|
||||||
|
}
|
||||||
|
const rounded = roundToStepPrecision(opacity, 0.05);
|
||||||
|
expect(rounded).toBeCloseTo(0.5, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle percentage calculations with step=0.01', () => {
|
||||||
|
// Calculate percentage with floating-point issues
|
||||||
|
const percentage = (1 / 3) * 100;
|
||||||
|
const rounded = roundToStepPrecision(percentage, 0.01);
|
||||||
|
expect(rounded).toBeCloseTo(33.33, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle coordinate rounding with step=0.000001', () => {
|
||||||
|
// GPS coordinates with micro-degree precision
|
||||||
|
const lat = 40.7128 + 0.000001;
|
||||||
|
const rounded = roundToStepPrecision(lat, 0.000001);
|
||||||
|
expect(rounded).toBeCloseTo(40.712801, 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle time values with step=0.001', () => {
|
||||||
|
// Millisecond precision timing
|
||||||
|
const time = 123.456 + 0.001 + 0.001;
|
||||||
|
const rounded = roundToStepPrecision(time, 0.001);
|
||||||
|
expect(rounded).toBeCloseTo(123.458, 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('common step values', () => {
|
||||||
|
test('should correctly round for step=0.05', () => {
|
||||||
|
// step=0.05 has 2 decimal places, so it rounds to 2 decimal places
|
||||||
|
// Note: This rounds to the DECIMAL PRECISION, not to the step increment
|
||||||
|
expect(roundToStepPrecision(1.34, 0.05)).toBeCloseTo(1.34);
|
||||||
|
expect(roundToStepPrecision(1.36, 0.05)).toBeCloseTo(1.36);
|
||||||
|
expect(roundToStepPrecision(1.37, 0.05)).toBeCloseTo(1.37);
|
||||||
|
expect(roundToStepPrecision(1.38, 0.05)).toBeCloseTo(1.38);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should correctly round for step=0.25', () => {
|
||||||
|
// step=0.25 has 2 decimal places, so it rounds to 2 decimal places
|
||||||
|
// Note: This rounds to the DECIMAL PRECISION, not to the step increment
|
||||||
|
expect(roundToStepPrecision(1.24, 0.25)).toBeCloseTo(1.24);
|
||||||
|
expect(roundToStepPrecision(1.26, 0.25)).toBeCloseTo(1.26);
|
||||||
|
expect(roundToStepPrecision(1.37, 0.25)).toBeCloseTo(1.37);
|
||||||
|
expect(roundToStepPrecision(1.38, 0.25)).toBeCloseTo(1.38);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should correctly round for step=0.1', () => {
|
||||||
|
// step=0.1 has 1 decimal place, so it rounds to 1 decimal place
|
||||||
|
expect(roundToStepPrecision(1.04, 0.1)).toBeCloseTo(1.0);
|
||||||
|
expect(roundToStepPrecision(1.05, 0.1)).toBeCloseTo(1.1);
|
||||||
|
expect(roundToStepPrecision(1.14, 0.1)).toBeCloseTo(1.1);
|
||||||
|
expect(roundToStepPrecision(1.15, 0.1)).toBeCloseTo(1.1); // standard banker's rounding
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should correctly round for step=0.01', () => {
|
||||||
|
expect(roundToStepPrecision(1.234, 0.01)).toBeCloseTo(1.23);
|
||||||
|
expect(roundToStepPrecision(1.235, 0.01)).toBeCloseTo(1.24);
|
||||||
|
expect(roundToStepPrecision(1.236, 0.01)).toBeCloseTo(1.24);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('integration with getDecimalPlaces', () => {
|
||||||
|
test('should use correct decimal places from step parameter', () => {
|
||||||
|
// step=0.1 has 1 decimal place
|
||||||
|
expect(roundToStepPrecision(1.234, 0.1)).toBeCloseTo(1.2);
|
||||||
|
// step=0.01 has 2 decimal places
|
||||||
|
expect(roundToStepPrecision(1.234, 0.01)).toBeCloseTo(1.23);
|
||||||
|
// step=0.001 has 3 decimal places
|
||||||
|
expect(roundToStepPrecision(1.2345, 0.001)).toBeCloseTo(1.235);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle steps with different precisions correctly', () => {
|
||||||
|
const value = 1.123456789;
|
||||||
|
|
||||||
|
expect(roundToStepPrecision(value, 0.1)).toBeCloseTo(1.1);
|
||||||
|
expect(roundToStepPrecision(value, 0.01)).toBeCloseTo(1.12);
|
||||||
|
expect(roundToStepPrecision(value, 0.001)).toBeCloseTo(1.123);
|
||||||
|
expect(roundToStepPrecision(value, 0.0001)).toBeCloseTo(1.1235);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('return type behavior', () => {
|
||||||
|
test('should return finite number for valid inputs', () => {
|
||||||
|
expect(Number.isFinite(roundToStepPrecision(1.23, 0.01))).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('precision edge cases', () => {
|
||||||
|
test('should round 0.9999 correctly with step=0.01', () => {
|
||||||
|
expect(roundToStepPrecision(0.9999, 0.01)).toBeCloseTo(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should round 0.99999 correctly with step=0.001', () => {
|
||||||
|
expect(roundToStepPrecision(0.99999, 0.001)).toBeCloseTo(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle rounding up to next integer', () => {
|
||||||
|
expect(roundToStepPrecision(0.999, 0.001)).toBeCloseTo(0.999);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle values just below step boundary', () => {
|
||||||
|
expect(roundToStepPrecision(1.4999, 0.01)).toBeCloseTo(1.5);
|
||||||
|
expect(roundToStepPrecision(1.499, 0.01)).toBeCloseTo(1.5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user