Full document, spreadsheet, slideshow, and diagram tooling
1import { describe, it, expect } from 'vitest';
2import {
3 createArrayResult,
4 columnVector,
5 rowVector,
6 computeSpillRange,
7 spillCells,
8 isInSpillRange,
9 spillRangesOverlap,
10 arrayUnique,
11 arraySort,
12 arrayFilter,
13 arrayTranspose,
14 arraySequence,
15 arrayFlatten,
16 getArrayValue,
17} from '../src/sheets/array-formulas.js';
18
19describe('createArrayResult', () => {
20 it('creates from 2D array', () => {
21 const r = createArrayResult([[1, 2], [3, 4]]);
22 expect(r.rows).toBe(2);
23 expect(r.cols).toBe(2);
24 expect(r.values).toEqual([[1, 2], [3, 4]]);
25 });
26
27 it('normalizes ragged arrays', () => {
28 const r = createArrayResult([[1, 2, 3], [4]]);
29 expect(r.cols).toBe(3);
30 expect(r.values[1]).toEqual([4, null, null]);
31 });
32
33 it('handles empty array', () => {
34 const r = createArrayResult([]);
35 expect(r.rows).toBe(0);
36 expect(r.cols).toBe(0);
37 });
38});
39
40describe('columnVector', () => {
41 it('creates column from flat array', () => {
42 const r = columnVector([1, 2, 3]);
43 expect(r.rows).toBe(3);
44 expect(r.cols).toBe(1);
45 expect(r.values).toEqual([[1], [2], [3]]);
46 });
47});
48
49describe('rowVector', () => {
50 it('creates row from flat array', () => {
51 const r = rowVector([1, 2, 3]);
52 expect(r.rows).toBe(1);
53 expect(r.cols).toBe(3);
54 expect(r.values).toEqual([[1, 2, 3]]);
55 });
56});
57
58describe('computeSpillRange', () => {
59 it('computes range from origin and result', () => {
60 const result = createArrayResult([[1, 2], [3, 4], [5, 6]]);
61 const range = computeSpillRange(2, 3, result);
62 expect(range).toEqual({ originRow: 2, originCol: 3, rows: 3, cols: 2 });
63 });
64});
65
66describe('spillCells', () => {
67 it('lists all cells in range', () => {
68 const range = { originRow: 1, originCol: 1, rows: 2, cols: 3 };
69 const cells = spillCells(range);
70 expect(cells).toHaveLength(6);
71 expect(cells[0]).toEqual({ row: 1, col: 1 });
72 expect(cells[5]).toEqual({ row: 2, col: 3 });
73 });
74});
75
76describe('isInSpillRange', () => {
77 const range = { originRow: 2, originCol: 3, rows: 3, cols: 2 };
78
79 it('returns true for cell inside range', () => {
80 expect(isInSpillRange(3, 4, range)).toBe(true);
81 });
82
83 it('returns true for origin cell', () => {
84 expect(isInSpillRange(2, 3, range)).toBe(true);
85 });
86
87 it('returns false for cell outside range', () => {
88 expect(isInSpillRange(1, 3, range)).toBe(false);
89 expect(isInSpillRange(5, 3, range)).toBe(false);
90 expect(isInSpillRange(2, 5, range)).toBe(false);
91 });
92});
93
94describe('spillRangesOverlap', () => {
95 it('detects overlapping ranges', () => {
96 const a = { originRow: 0, originCol: 0, rows: 3, cols: 3 };
97 const b = { originRow: 2, originCol: 2, rows: 2, cols: 2 };
98 expect(spillRangesOverlap(a, b)).toBe(true);
99 });
100
101 it('detects non-overlapping ranges', () => {
102 const a = { originRow: 0, originCol: 0, rows: 2, cols: 2 };
103 const b = { originRow: 3, originCol: 3, rows: 2, cols: 2 };
104 expect(spillRangesOverlap(a, b)).toBe(false);
105 });
106
107 it('detects adjacent (non-overlapping)', () => {
108 const a = { originRow: 0, originCol: 0, rows: 2, cols: 2 };
109 const b = { originRow: 0, originCol: 2, rows: 2, cols: 2 };
110 expect(spillRangesOverlap(a, b)).toBe(false);
111 });
112});
113
114describe('arrayUnique', () => {
115 it('removes duplicate rows', () => {
116 const r = createArrayResult([[1, 'a'], [2, 'b'], [1, 'a'], [3, 'c']]);
117 const u = arrayUnique(r);
118 expect(u.rows).toBe(3);
119 expect(u.values).toEqual([[1, 'a'], [2, 'b'], [3, 'c']]);
120 });
121});
122
123describe('arraySort', () => {
124 it('sorts by first column ascending', () => {
125 const r = createArrayResult([[3, 'c'], [1, 'a'], [2, 'b']]);
126 const s = arraySort(r, 0, true);
127 expect(s.values).toEqual([[1, 'a'], [2, 'b'], [3, 'c']]);
128 });
129
130 it('sorts descending', () => {
131 const r = createArrayResult([[1], [3], [2]]);
132 const s = arraySort(r, 0, false);
133 expect(s.values).toEqual([[3], [2], [1]]);
134 });
135
136 it('sorts strings', () => {
137 const r = createArrayResult([['banana'], ['apple'], ['cherry']]);
138 const s = arraySort(r, 0, true);
139 expect(s.values[0][0]).toBe('apple');
140 });
141});
142
143describe('arrayFilter', () => {
144 it('filters rows by predicate', () => {
145 const r = createArrayResult([[1, 'a'], [2, 'b'], [3, 'c']]);
146 const f = arrayFilter(r, 0, v => (v as number) > 1);
147 expect(f.rows).toBe(2);
148 expect(f.values).toEqual([[2, 'b'], [3, 'c']]);
149 });
150
151 it('returns empty when nothing matches', () => {
152 const r = createArrayResult([[1], [2]]);
153 const f = arrayFilter(r, 0, v => (v as number) > 10);
154 expect(f.rows).toBe(0);
155 });
156});
157
158describe('arrayTranspose', () => {
159 it('swaps rows and columns', () => {
160 const r = createArrayResult([[1, 2, 3], [4, 5, 6]]);
161 const t = arrayTranspose(r);
162 expect(t.rows).toBe(3);
163 expect(t.cols).toBe(2);
164 expect(t.values).toEqual([[1, 4], [2, 5], [3, 6]]);
165 });
166
167 it('handles empty array', () => {
168 const t = arrayTranspose(createArrayResult([]));
169 expect(t.rows).toBe(0);
170 });
171});
172
173describe('arraySequence', () => {
174 it('generates sequence', () => {
175 const r = arraySequence(3, 1, 1, 1);
176 expect(r.values).toEqual([[1], [2], [3]]);
177 });
178
179 it('generates 2D sequence', () => {
180 const r = arraySequence(2, 3, 10, 5);
181 expect(r.values).toEqual([[10, 15, 20], [25, 30, 35]]);
182 });
183});
184
185describe('arrayFlatten', () => {
186 it('flattens 2D to column', () => {
187 const r = createArrayResult([[1, 2], [3, 4]]);
188 const f = arrayFlatten(r);
189 expect(f.rows).toBe(4);
190 expect(f.cols).toBe(1);
191 expect(f.values).toEqual([[1], [2], [3], [4]]);
192 });
193});
194
195describe('getArrayValue', () => {
196 it('returns value at position', () => {
197 const r = createArrayResult([[1, 2], [3, 4]]);
198 expect(getArrayValue(r, 0, 0)).toBe(1);
199 expect(getArrayValue(r, 1, 1)).toBe(4);
200 });
201
202 it('returns null for out-of-bounds', () => {
203 const r = createArrayResult([[1]]);
204 expect(getArrayValue(r, 5, 0)).toBeNull();
205 expect(getArrayValue(r, 0, 5)).toBeNull();
206 expect(getArrayValue(r, -1, 0)).toBeNull();
207 });
208});