UNPKG

@tldraw/utils

Version:

tldraw infinite canvas SDK (private utilities).

340 lines (276 loc) • 9.74 kB
import { areArraysShallowEqual, compact, dedupe, last, maxBy, mergeArraysAndReplaceDefaults, minBy, partition, rotateArray, } from './array' describe('rotateArray', () => { test('should rotate array to the left with positive offset', () => { // Based on JSDoc examples, this is the expected behavior expect(rotateArray([1, 2, 3, 4], 1)).toEqual([2, 3, 4, 1]) expect(rotateArray([1, 2, 3, 4], 2)).toEqual([3, 4, 1, 2]) }) test('should rotate array to the left with negative offset', () => { // Based on JSDoc examples, this is the expected behavior expect(rotateArray([1, 2, 3, 4], -1)).toEqual([2, 3, 4, 1]) expect(rotateArray([1, 2, 3, 4], -2)).toEqual([3, 4, 1, 2]) }) it('should handle zero offset', () => { expect(rotateArray([1, 2, 3, 4], 0)).toEqual([1, 2, 3, 4]) }) test('should handle offset larger than array length', () => { // Based on understanding of rotation: offset 5 on length 3 should be same as offset 2 expect(rotateArray([1, 2, 3], 5)).toEqual([3, 1, 2]) // offset -5 on length 3 should be same as offset -2 which should be same as offset 2 expect(rotateArray([1, 2, 3], -5)).toEqual([3, 1, 2]) }) it('should handle empty array', () => { expect(rotateArray([], 1)).toEqual([]) }) test('should work with different types', () => { // Based on JSDoc examples, this is the expected behavior expect(rotateArray(['a', 'b', 'c'], 2)).toEqual(['c', 'a', 'b']) }) }) describe('dedupe', () => { it('should remove duplicate primitives', () => { expect(dedupe([1, 2, 2, 3, 1])).toEqual([1, 2, 3]) expect(dedupe(['a', 'b', 'a', 'c'])).toEqual(['a', 'b', 'c']) }) it('should preserve order of first occurrence', () => { expect(dedupe([3, 1, 2, 1, 3])).toEqual([3, 1, 2]) }) it('should handle empty array', () => { expect(dedupe([])).toEqual([]) }) it('should handle array with no duplicates', () => { expect(dedupe([1, 2, 3])).toEqual([1, 2, 3]) }) it('should use custom equality function', () => { const objects = [ { id: 1, name: 'a' }, { id: 2, name: 'b' }, { id: 1, name: 'c' }, ] expect(dedupe(objects, (a, b) => a.id === b.id)).toEqual([ { id: 1, name: 'a' }, { id: 2, name: 'b' }, ]) }) it('should handle objects without custom equality', () => { const obj1 = { id: 1 } const obj2 = { id: 2 } expect(dedupe([obj1, obj2, obj1])).toEqual([obj1, obj2]) }) }) describe('compact', () => { it('should remove null and undefined values', () => { expect(compact([1, null, 2, undefined, 3])).toEqual([1, 2, 3]) }) it('should preserve falsy values that are not null/undefined', () => { expect(compact([0, false, '', null, undefined, 'hello'])).toEqual([0, false, '', 'hello']) }) it('should handle empty array', () => { expect(compact([])).toEqual([]) }) it('should handle array with only null/undefined', () => { expect(compact([null, undefined, null])).toEqual([]) }) it('should handle array with no null/undefined', () => { expect(compact([1, 2, 3])).toEqual([1, 2, 3]) }) }) describe('last', () => { it('should return last element of array', () => { expect(last([1, 2, 3])).toBe(3) expect(last(['a', 'b', 'c'])).toBe('c') }) it('should return undefined for empty array', () => { expect(last([])).toBeUndefined() }) it('should work with single element array', () => { expect(last([42])).toBe(42) }) it('should work with readonly arrays', () => { const readonlyArr: readonly number[] = [1, 2, 3] expect(last(readonlyArr)).toBe(3) }) }) describe('minBy', () => { it('should find item with minimum value', () => { const people = [ { name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }, { name: 'Charlie', age: 35 }, ] expect(minBy(people, (p) => p.age)).toEqual({ name: 'Bob', age: 25 }) }) it('should work with numbers', () => { expect(minBy([3, 1, 4, 1, 5], (x) => x)).toBe(1) }) it('should return undefined for empty array', () => { expect(minBy([], (x) => x)).toBeUndefined() }) it('should handle ties by returning first occurrence', () => { const items = [{ val: 5 }, { val: 3 }, { val: 3 }, { val: 7 }] expect(minBy(items, (x) => x.val)).toEqual({ val: 3 }) }) it('should work with negative values', () => { expect(minBy([-1, -5, -3], (x) => x)).toBe(-5) }) }) describe('maxBy', () => { it('should find item with maximum value', () => { const people = [ { name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }, { name: 'Charlie', age: 35 }, ] expect(maxBy(people, (p) => p.age)).toEqual({ name: 'Charlie', age: 35 }) }) it('should work with numbers', () => { expect(maxBy([3, 1, 4, 1, 5], (x) => x)).toBe(5) }) it('should return undefined for empty array', () => { expect(maxBy([], (x) => x)).toBeUndefined() }) it('should handle ties by returning first occurrence', () => { const items = [{ val: 5 }, { val: 7 }, { val: 7 }, { val: 3 }] expect(maxBy(items, (x) => x.val)).toEqual({ val: 7 }) }) it('should work with negative values', () => { expect(maxBy([-1, -5, -3], (x) => x)).toBe(-1) }) }) describe('partition', () => { it('should split array based on predicate', () => { const [evens, odds] = partition([1, 2, 3, 4, 5], (x) => x % 2 === 0) expect(evens).toEqual([2, 4]) expect(odds).toEqual([1, 3, 5]) }) it('should preserve order within partitions', () => { const [adults, minors] = partition( [ { name: 'Alice', age: 30 }, { name: 'Bob', age: 17 }, { name: 'Charlie', age: 25 }, ], (person) => person.age >= 18 ) expect(adults).toEqual([ { name: 'Alice', age: 30 }, { name: 'Charlie', age: 25 }, ]) expect(minors).toEqual([{ name: 'Bob', age: 17 }]) }) it('should handle empty array', () => { const [satisfies, doesNotSatisfy] = partition([], (x) => x > 0) expect(satisfies).toEqual([]) expect(doesNotSatisfy).toEqual([]) }) it('should handle all items satisfying predicate', () => { const [satisfies, doesNotSatisfy] = partition([2, 4, 6], (x) => x % 2 === 0) expect(satisfies).toEqual([2, 4, 6]) expect(doesNotSatisfy).toEqual([]) }) it('should handle no items satisfying predicate', () => { const [satisfies, doesNotSatisfy] = partition([1, 3, 5], (x) => x % 2 === 0) expect(satisfies).toEqual([]) expect(doesNotSatisfy).toEqual([1, 3, 5]) }) }) describe('areArraysShallowEqual', () => { it('should return true for identical arrays', () => { expect(areArraysShallowEqual([1, 2, 3], [1, 2, 3])).toBe(true) expect(areArraysShallowEqual(['a', 'b'], ['a', 'b'])).toBe(true) }) it('should return true for same reference', () => { const arr = [1, 2, 3] expect(areArraysShallowEqual(arr, arr)).toBe(true) }) it('should return false for different lengths', () => { expect(areArraysShallowEqual([1, 2], [1, 2, 3])).toBe(false) }) it('should return false for different elements', () => { expect(areArraysShallowEqual([1, 2, 3], [1, 2, 4])).toBe(false) }) it('should return true for empty arrays', () => { expect(areArraysShallowEqual([], [])).toBe(true) }) it('should use Object.is for comparison', () => { expect(areArraysShallowEqual([NaN], [NaN])).toBe(true) expect(areArraysShallowEqual([0], [-0])).toBe(false) }) it('should work with object references', () => { const obj = { x: 1 } expect(areArraysShallowEqual([obj], [obj])).toBe(true) expect(areArraysShallowEqual([{ x: 1 }], [{ x: 1 }])).toBe(false) }) it('should work with readonly arrays', () => { const arr1: readonly number[] = [1, 2, 3] const arr2: readonly number[] = [1, 2, 3] expect(areArraysShallowEqual(arr1, arr2)).toBe(true) }) }) describe('mergeArraysAndReplaceDefaults', () => { it('should merge custom entries with defaults, allowing custom entries to override defaults', () => { const defaults = [ { id: 'select', name: 'Default Select' }, { id: 'draw', name: 'Default Draw' }, { id: 'eraser', name: 'Default Eraser' }, ] const customEntries = [ { id: 'select', name: 'Custom Select' }, { id: 'custom-tool', name: 'Custom Tool' }, ] const result = mergeArraysAndReplaceDefaults('id', customEntries, defaults) expect(result).toEqual([ { id: 'draw', name: 'Default Draw' }, { id: 'eraser', name: 'Default Eraser' }, { id: 'select', name: 'Custom Select' }, { id: 'custom-tool', name: 'Custom Tool' }, ]) }) it('should handle empty custom entries', () => { const defaults = [ { id: 'select', name: 'Default Select' }, { id: 'draw', name: 'Default Draw' }, ] const customEntries: typeof defaults = [] const result = mergeArraysAndReplaceDefaults('id', customEntries, defaults) expect(result).toEqual(defaults) }) it('should handle empty defaults', () => { const defaults: Array<{ id: string; name: string }> = [] const customEntries = [{ id: 'custom-tool', name: 'Custom Tool' }] const result = mergeArraysAndReplaceDefaults('id', customEntries, defaults) expect(result).toEqual(customEntries) }) it('should handle both empty arrays', () => { const defaults: Array<{ id: string; name: string }> = [] const customEntries: Array<{ id: string; name: string }> = [] const result = mergeArraysAndReplaceDefaults('id', customEntries, defaults) expect(result).toEqual([]) }) it('should work with different key names', () => { const defaults = [ { type: 'text', name: 'Default Text' }, { type: 'geo', name: 'Default Geo' }, ] const customEntries = [ { type: 'text', name: 'Custom Text' }, { type: 'custom', name: 'Custom Shape' }, ] const result = mergeArraysAndReplaceDefaults('type', customEntries, defaults) expect(result).toEqual([ { type: 'geo', name: 'Default Geo' }, { type: 'text', name: 'Custom Text' }, { type: 'custom', name: 'Custom Shape' }, ]) }) })