/**
* Performance Audit Tests
*
* Tests for verifying performance optimization compliance.
* Validates LOD, clustering, progressive loading, and frame rate control.
*
* Requirements: 1.4, 11.1-11.6
*/
import { describe, expect, it } from 'vitest';
import {
CLUSTERING_THRESHOLD,
DEFAULT_FRAME_RATE_CONFIG,
DEFAULT_LOD_LEVELS,
calculateDistance,
calculateDistanceSquared,
calculateFrameInterval,
calculateLODLevel,
calculateLoadingPriority,
filterByViewDistance,
getLODLevelIndex,
interpolateFrameRate,
isWithinViewDistance,
shouldEnableClustering,
sortByLoadingPriority,
} from '../../utils/performance';
// ============================================================================
// LOD TESTS (Requirements: 11.1)
// ============================================================================
describe('Level-of-Detail (LOD) Audit', () => {
describe('calculateLODLevel', () => {
it('should return highest detail for close distances', () => {
const level = calculateLODLevel(5);
expect(level.segments).toBe(32);
expect(level.showLabel).toBe(true);
expect(level.showGlow).toBe(true);
expect(level.opacityMultiplier).toBe(1.0);
});
it('should return medium detail for medium distances', () => {
const level = calculateLODLevel(30);
expect(level.segments).toBeLessThan(32);
expect(level.opacityMultiplier).toBeLessThan(1.0);
});
it('should return lowest detail for far distances', () => {
const level = calculateLODLevel(150);
expect(level.segments).toBe(4);
expect(level.showLabel).toBe(false);
expect(level.showGlow).toBe(false);
expect(level.opacityMultiplier).toBeLessThan(0.5);
});
it('should have 5 LOD levels defined', () => {
expect(DEFAULT_LOD_LEVELS.length).toBe(5);
});
it('should have decreasing detail with increasing distance', () => {
const distances = [5, 15, 30, 75, 150];
const levels = distances.map((d) => calculateLODLevel(d));
for (let i = 1; i < levels.length; i++) {
const current = levels[i];
const previous = levels[i - 1];
if (current && previous) {
expect(current.segments).toBeLessThanOrEqual(previous.segments);
expect(current.opacityMultiplier).toBeLessThanOrEqual(previous.opacityMultiplier);
}
}
});
});
describe('getLODLevelIndex', () => {
it('should return 0 for closest distances', () => {
expect(getLODLevelIndex(5)).toBe(0);
});
it('should return last index for far distances', () => {
expect(getLODLevelIndex(1000)).toBe(DEFAULT_LOD_LEVELS.length - 1);
});
});
});
// ============================================================================
// CLUSTERING TESTS (Requirements: 11.3)
// ============================================================================
describe('Clustering Audit', () => {
describe('shouldEnableClustering', () => {
it('should enable clustering when node count exceeds threshold', () => {
expect(shouldEnableClustering(1001)).toBe(true);
expect(shouldEnableClustering(2000)).toBe(true);
});
it('should disable clustering when node count is below threshold', () => {
expect(shouldEnableClustering(999)).toBe(false);
expect(shouldEnableClustering(500)).toBe(false);
});
it('should disable clustering at exactly threshold', () => {
expect(shouldEnableClustering(CLUSTERING_THRESHOLD)).toBe(false);
});
it('should use default threshold of 1000', () => {
expect(CLUSTERING_THRESHOLD).toBe(1000);
});
});
});
// ============================================================================
// PROGRESSIVE LOADING TESTS (Requirements: 11.5)
// ============================================================================
describe('Progressive Loading Audit', () => {
describe('calculateLoadingPriority', () => {
it('should return 0 for distance 0 (highest priority)', () => {
expect(calculateLoadingPriority(0)).toBe(0);
});
it('should return 1 for max distance (lowest priority)', () => {
expect(calculateLoadingPriority(100, 100)).toBe(1);
});
it('should return proportional priority for intermediate distances', () => {
expect(calculateLoadingPriority(50, 100)).toBe(0.5);
expect(calculateLoadingPriority(25, 100)).toBe(0.25);
});
it('should clamp to 1 for distances beyond max', () => {
expect(calculateLoadingPriority(200, 100)).toBe(1);
});
});
describe('sortByLoadingPriority', () => {
it('should sort nodes by distance (closest first)', () => {
const nodes = [
{ id: 'far', distance: 100 },
{ id: 'close', distance: 10 },
{ id: 'medium', distance: 50 },
];
const sorted = sortByLoadingPriority(nodes);
expect(sorted[0]?.id).toBe('close');
expect(sorted[1]?.id).toBe('medium');
expect(sorted[2]?.id).toBe('far');
});
it('should not mutate original array', () => {
const nodes = [
{ id: 'a', distance: 50 },
{ id: 'b', distance: 10 },
];
const original = [...nodes];
sortByLoadingPriority(nodes);
expect(nodes).toEqual(original);
});
});
});
// ============================================================================
// FRAME RATE CONTROL TESTS (Requirements: 11.6)
// ============================================================================
describe('Frame Rate Control Audit', () => {
describe('calculateFrameInterval', () => {
it('should calculate correct interval for 60fps', () => {
const interval = calculateFrameInterval(60);
expect(interval).toBeCloseTo(16.67, 1);
});
it('should calculate correct interval for 30fps', () => {
const interval = calculateFrameInterval(30);
expect(interval).toBeCloseTo(33.33, 1);
});
it('should calculate correct interval for 15fps (idle)', () => {
const interval = calculateFrameInterval(15);
expect(interval).toBeCloseTo(66.67, 1);
});
it('should handle invalid frame rate gracefully', () => {
const interval = calculateFrameInterval(0);
expect(interval).toBeGreaterThan(0);
});
});
describe('interpolateFrameRate', () => {
it('should return start rate at progress 0', () => {
expect(interpolateFrameRate(60, 15, 0)).toBe(60);
});
it('should return end rate at progress 1', () => {
expect(interpolateFrameRate(60, 15, 1)).toBe(15);
});
it('should interpolate correctly at 0.5', () => {
expect(interpolateFrameRate(60, 20, 0.5)).toBe(40);
});
it('should clamp progress to 0-1 range', () => {
expect(interpolateFrameRate(60, 15, -0.5)).toBe(60);
expect(interpolateFrameRate(60, 15, 1.5)).toBe(15);
});
});
describe('DEFAULT_FRAME_RATE_CONFIG', () => {
it('should have 60fps active frame rate', () => {
expect(DEFAULT_FRAME_RATE_CONFIG.activeFrameRate).toBe(60);
});
it('should have 15fps idle frame rate', () => {
expect(DEFAULT_FRAME_RATE_CONFIG.idleFrameRate).toBe(15);
});
it('should have 3 second idle timeout', () => {
expect(DEFAULT_FRAME_RATE_CONFIG.idleTimeout).toBe(3000);
});
});
});
// ============================================================================
// DISTANCE CALCULATION TESTS
// ============================================================================
describe('Distance Calculations', () => {
describe('calculateDistance', () => {
it('should calculate correct distance for simple cases', () => {
expect(calculateDistance([0, 0, 0], [3, 4, 0])).toBe(5);
expect(calculateDistance([0, 0, 0], [0, 0, 10])).toBe(10);
});
it('should return 0 for same points', () => {
expect(calculateDistance([5, 5, 5], [5, 5, 5])).toBe(0);
});
});
describe('calculateDistanceSquared', () => {
it('should calculate squared distance correctly', () => {
expect(calculateDistanceSquared([0, 0, 0], [3, 4, 0])).toBe(25);
});
it('should be faster than regular distance for comparisons', () => {
// Squared distance avoids sqrt operation
const p1: [number, number, number] = [0, 0, 0];
const p2: [number, number, number] = [3, 4, 5];
const distSq = calculateDistanceSquared(p1, p2);
const dist = calculateDistance(p1, p2);
// Use toBeCloseTo for floating point comparison
expect(distSq).toBeCloseTo(dist * dist, 10);
});
});
});
// ============================================================================
// VISIBILITY CULLING TESTS
// ============================================================================
describe('Visibility Culling', () => {
describe('isWithinViewDistance', () => {
it('should return true for points within distance', () => {
expect(isWithinViewDistance([5, 0, 0], [0, 0, 0], 10)).toBe(true);
});
it('should return false for points beyond distance', () => {
expect(isWithinViewDistance([15, 0, 0], [0, 0, 0], 10)).toBe(false);
});
it('should return true for points exactly at distance', () => {
expect(isWithinViewDistance([10, 0, 0], [0, 0, 0], 10)).toBe(true);
});
});
describe('filterByViewDistance', () => {
it('should filter out nodes beyond view distance', () => {
const nodes = [
{ id: 'close', position: [5, 0, 0] as [number, number, number] },
{ id: 'far', position: [50, 0, 0] as [number, number, number] },
{ id: 'medium', position: [15, 0, 0] as [number, number, number] },
];
const visible = filterByViewDistance(nodes, [0, 0, 0], 20);
expect(visible.length).toBe(2);
expect(visible.map((n) => n.id)).toContain('close');
expect(visible.map((n) => n.id)).toContain('medium');
expect(visible.map((n) => n.id)).not.toContain('far');
});
});
});
// ============================================================================
// MINIMUM FRAME RATE TESTS (Requirements: 1.4)
// ============================================================================
describe('Minimum Frame Rate Compliance', () => {
it('should maintain minimum 30fps target', () => {
// Requirements: 1.4 - maintain minimum frame rate of 30 FPS
const minFrameRate = 30;
const activeFrameRate = DEFAULT_FRAME_RATE_CONFIG.activeFrameRate;
expect(activeFrameRate).toBeGreaterThanOrEqual(minFrameRate);
});
it('should have idle frame rate above 0', () => {
expect(DEFAULT_FRAME_RATE_CONFIG.idleFrameRate).toBeGreaterThan(0);
});
});