diff --git a/src/components/GradientInput/index.tsx b/src/components/GradientInput/index.tsx index bd225e93..d0755674 100644 --- a/src/components/GradientInput/index.tsx +++ b/src/components/GradientInput/index.tsx @@ -85,6 +85,7 @@ const GradientInput = (props: GradientInputProps): JSX.Element => { value={gradientStrengthData.uiValue} step={0.01} style={{ width: 100 }} + tooltip={{ formatter: (value) => value?.toFixed(2) }} /> { + describe('toUi', () => { + test('converts decay_length to gradient strength using inverse relationship', () => { + expect(toUi(0.01)).toBe(100); + expect(toUi(0.02)).toBe(50); + expect(toUi(0.1)).toBe(10); + expect(toUi(1)).toBe(1); + expect(toUi(10)).toBe(0.1); + expect(toUi(100)).toBe(0.01); + }); + + test('rounds to 2 decimal places for display', () => { + expect(toUi(0.03)).toBe(33.33); + expect(toUi(0.07)).toBe(14.29); + expect(toUi(0.15)).toBe(6.67); + }); + + test('handles edge cases', () => { + expect(toUi(0)).toBe(100); + expect(toUi(-5)).toBe(100); + }); + }); + + describe('toStore', () => { + test('converts gradient strength to decay_length using inverse relationship', () => { + expect(toStore(100)).toBe(0.01); + expect(toStore(50)).toBe(0.02); + expect(toStore(10)).toBe(0.1); + expect(toStore(1)).toBe(1); + }); + + test('maintains precision across full range', () => { + expect(toStore(60)).toBe(0.01666667); + expect(toStore(75)).toBe(0.01333333); + expect(toStore(80)).toBe(0.0125); + expect(toStore(90)).toBe(0.01111111); + expect(toStore(99)).toBe(0.01010101); + }); + + test('produces distinct values for different inputs', () => { + const values = [50, 51, 60, 75, 90, 100]; + const storeValues = values.map(toStore); + const uniqueValues = new Set(storeValues); + expect(uniqueValues.size).toBe(values.length); + }); + + test('handles edge cases', () => { + expect(toStore(0)).toBe(100); + expect(toStore(-5)).toBe(100); + }); + }); + + describe('round2', () => { + test('rounds numbers to 2 decimal places', () => { + expect(round2(1.234)).toBe(1.23); + expect(round2(5.678)).toBe(5.68); + expect(round2(9.999)).toBe(10); + }); + + test('handles integers', () => { + expect(round2(5)).toBe(5); + expect(round2(100)).toBe(100); + }); + + test('handles small values', () => { + expect(round2(0.001)).toBe(0); + expect(round2(0.005)).toBe(0.01); + expect(round2(0.999)).toBe(1); + }); + }); + + describe('Conversion', () => { + test('UI -> Store -> UI maintains value', () => { + const uiValues = [0.01, 1, 10, 25, 50, 60, 100]; + + uiValues.forEach(uiVal => { + const storeVal = toStore(uiVal); + const backToUi = toUi(storeVal); + expect(backToUi).toBeCloseTo(uiVal, 1); + }); + }); + + test('Store -> UI -> Store maintains value', () => { + const storeValues = [0.01, 0.02, 0.05, 0.1, 0.5, 1, 10]; + + storeValues.forEach(storeVal => { + const uiVal = toUi(storeVal); + const backToStore = toStore(uiVal); + expect(backToStore).toBeCloseTo(storeVal, 4); + }); + }); + }); + + }); + diff --git a/src/utils/gradient.ts b/src/utils/gradient.ts index 857f9a09..62ab4ed9 100644 --- a/src/utils/gradient.ts +++ b/src/utils/gradient.ts @@ -10,9 +10,18 @@ interface GradientStrength { }; // Helpers: store <-> UI mapping -// Store: "smaller = stronger" (e.g., decay length). UI: "bigger = stronger" (0..1). -export const toUi = (storeVal: number) => Number((1 - storeVal).toFixed(2)); -export const toStore = (uiVal: number) => Number((1 - uiVal).toFixed(2)); +// Store: "smaller = stronger" (e.g., decay length). UI: "bigger = stronger" (0.01-100). decay_length = 1 / gradient strength +const MIN_DECAY_LENGTH = 0.01; +const MAX_DECAY_LENGTH = 100; + +export const toUi = (storeVal: number) => { + if (storeVal <= 0) return 1 / MIN_DECAY_LENGTH; // the max UI value due to the inverse relationship—smaller decay length = stronger gradient = higher UI number. + return Number((1 / storeVal).toFixed(2)); +}; +export const toStore = (uiVal: number) => { + if (uiVal <= 0) return MAX_DECAY_LENGTH; + return Number((1 / uiVal).toFixed(8)); +}; export const round2 = (n: number) => Number(n.toFixed(2)); @@ -60,8 +69,8 @@ export function deriveGradientStrength( ): GradientStrength | undefined { if (!opt?.strength_path) return undefined; - const storeMin = opt.strength_min ?? 0; - const storeMax = opt.strength_max ?? 0.99; + const storeMin = opt.strength_min ?? MIN_DECAY_LENGTH; + const storeMax = opt.strength_max ?? MAX_DECAY_LENGTH; const uiMin = toUi(storeMax); const uiMax = toUi(storeMin); @@ -71,7 +80,7 @@ export function deriveGradientStrength( const storeNum = typeof storeRaw === "number" ? storeRaw - : opt.strength_default ?? storeMin; + : opt.strength_default ?? MIN_DECAY_LENGTH; const uiValue = round2(clampUi(toUi(storeNum))); return {