diff --git a/src/shared/lib/utils/clampNumber/clampNumber.test.ts b/src/shared/lib/utils/clampNumber/clampNumber.test.ts new file mode 100644 index 0000000..928c841 --- /dev/null +++ b/src/shared/lib/utils/clampNumber/clampNumber.test.ts @@ -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); + }); + }); +}); diff --git a/src/shared/lib/utils/getDecimalPlaces/getDecimalPlaces.test.ts b/src/shared/lib/utils/getDecimalPlaces/getDecimalPlaces.test.ts new file mode 100644 index 0000000..fcb5bc5 --- /dev/null +++ b/src/shared/lib/utils/getDecimalPlaces/getDecimalPlaces.test.ts @@ -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'); + }); + }); +}); diff --git a/src/shared/lib/utils/roundToStepPrecision/roundToStepPrecision.test.ts b/src/shared/lib/utils/roundToStepPrecision/roundToStepPrecision.test.ts new file mode 100644 index 0000000..3805408 --- /dev/null +++ b/src/shared/lib/utils/roundToStepPrecision/roundToStepPrecision.test.ts @@ -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); + }); + }); +});