import { describe, it, expect } from 'vitest'; import { resolveScale, DEFAULT_MARKER_RADIUS_PX } from './map-scale'; describe('resolveScale', () => { const domain: [number, number] = [0, 100]; const range: [number, number] = [4, 24]; it('maps the domain endpoints to the range endpoints', () => { expect(resolveScale({ value: 0, domain, range, method: 'linear' })).toBe(4); expect(resolveScale({ value: 100, domain, range, method: 'linear' })).toBe(24); }); it('interpolates linearly', () => { expect(resolveScale({ value: 50, domain, range, method: 'linear' })).toBe(14); }); it('defaults to sqrt so area, not radius, tracks the value', () => { // At the midpoint, sqrt(0.5) ≈ 0.707 -> further along the range than linear. const midpoint = resolveScale({ value: 50, domain, range }); expect(midpoint).toBeCloseTo(4 + Math.SQRT1_2 * 20, 5); expect(midpoint).toBeGreaterThan(resolveScale({ value: 50, domain, range, method: 'linear' })); }); it('quadruples the value to double the radius under sqrt', () => { const small = resolveScale({ value: 25, domain: [0, 400], range: [0, 40] }); const large = resolveScale({ value: 100, domain: [0, 400], range: [0, 40] }); expect(large).toBeCloseTo(small * 2, 5); }); it('clamps values outside the domain into the range', () => { expect(resolveScale({ value: -50, domain, range, method: 'linear' })).toBe(4); expect(resolveScale({ value: 500, domain, range, method: 'linear' })).toBe(24); }); it('accepts a reversed domain', () => { expect(resolveScale({ value: 0, domain: [100, 0], range, method: 'linear' })).toBe(4); expect(resolveScale({ value: 100, domain: [100, 0], range, method: 'linear' })).toBe(24); }); it('collapses a zero-width domain to the smallest size instead of NaN', () => { const result = resolveScale({ value: 7, domain: [7, 7], range }); expect(result).toBe(4); expect(Number.isNaN(result)).toBe(false); }); it('returns the smallest size for non-finite input', () => { // Infinity is not a data value, so it is rejected by the finite guard rather // than clamped to the top of the range. expect(resolveScale({ value: NaN, domain, range })).toBe(4); expect(resolveScale({ value: Infinity, domain, range })).toBe(4); expect(resolveScale({ value: 10, domain: [0, Infinity], range })).toBe(4); }); describe('log', () => { it('interpolates on a log scale', () => { const result = resolveScale({ value: 100, domain: [10, 1000], range: [0, 10], method: 'log', }); expect(result).toBeCloseTo(5, 5); }); it('falls back to the smallest size when the domain reaches zero', () => { // log(0) is undefined, so there is no honest mapping to produce here. expect(resolveScale({ value: 50, domain: [0, 100], range, method: 'log' })).toBe(4); }); it('falls back to the smallest size for a non-positive value', () => { expect(resolveScale({ value: 0, domain: [1, 100], range, method: 'log' })).toBe(4); }); }); it('supports a descending range', () => { expect(resolveScale({ value: 100, domain, range: [24, 4], method: 'linear' })).toBe(4); }); it('exposes a sensible default marker radius', () => { expect(DEFAULT_MARKER_RADIUS_PX).toBeGreaterThan(0); }); });