diff --git a/ui-speedspacechart/src/__tests__/utils.spec.ts b/ui-speedspacechart/src/__tests__/utils.spec.ts index 2cae58b8..9d1f4149 100644 --- a/ui-speedspacechart/src/__tests__/utils.spec.ts +++ b/ui-speedspacechart/src/__tests__/utils.spec.ts @@ -15,6 +15,8 @@ import { positionToPosX, interpolate, clamp, + filterVisibleElements, + type VisibilityFilterOptions, getSnappedStop, } from '../components/utils'; import type { LayerData, Store } from '../types/chartTypes'; @@ -361,6 +363,88 @@ describe('clamp', () => { }); }); +describe('filterVisibleElements', () => { + type Element = { id: number; position: number; weight: number | undefined }; + + const elements: Element[] = [ + { id: 1, position: 10, weight: 5 }, + { id: 2, position: 15, weight: 3 }, + { id: 3, position: 25, weight: 4 }, + { id: 4, position: 30, weight: undefined }, + { id: 5, position: 50, weight: 1 }, + ]; + + const getPosition = (element: Element) => element.position; + const getWeight = (element: Element) => element.weight; + + it('filters visible elements based on minSpace', () => { + const options: VisibilityFilterOptions = { + elements, + getPosition, + getWeight, + minSpace: 10, + }; + + const result = filterVisibleElements(options); + + expect(result).toEqual([ + { id: 1, position: 10, weight: 5 }, // Highest weight and valid position + { id: 3, position: 25, weight: 4 }, // Second highest weight with valid spacing + { id: 5, position: 50, weight: 1 }, // Last valid element + ]); + }); + + it('returns all elements if minSpace is 0', () => { + const options: VisibilityFilterOptions = { + elements, + getPosition, + getWeight, + minSpace: 0, // No space restriction + }; + + const result = filterVisibleElements(options); + + // All elements are sorted by position since there is no restriction + expect(result).toEqual(elements.sort((a, b) => a.position - b.position)); + }); + + it('returns an empty array if no elements are provided', () => { + const options: VisibilityFilterOptions = { + elements: [], + getPosition, + getWeight, + minSpace: 10, + }; + + const result = filterVisibleElements(options); + + expect(result).toEqual([]); + }); + + it('prioritizes higher weights when positions overlap', () => { + const overlappingElements: Element[] = [ + { id: 1, position: 10, weight: 5 }, + { id: 2, position: 12, weight: 6 }, // Overlaps with id: 1 + { id: 3, position: 25, weight: 4 }, + ]; + + const options: VisibilityFilterOptions = { + elements: overlappingElements, + getPosition, + getWeight, + minSpace: 10, + }; + + const result = filterVisibleElements(options); + + // id: 2 replaces id: 1 because of higher weight + expect(result).toEqual([ + { id: 2, position: 12, weight: 6 }, + { id: 3, position: 25, weight: 4 }, + ]); + }); +}); + describe('getSnappedStop', () => { const width = 200; it.each([ diff --git a/ui-speedspacechart/src/components/utils.ts b/ui-speedspacechart/src/components/utils.ts index 41660dd3..7b3facf0 100644 --- a/ui-speedspacechart/src/components/utils.ts +++ b/ui-speedspacechart/src/components/utils.ts @@ -15,7 +15,7 @@ type SlopesValues = { maxPosition: number; }; -type VisibilityFilterOptions = { +export type VisibilityFilterOptions = { elements: T[]; getPosition: (element: T) => number; getWeight: (element: T) => number | undefined;