@object-ui/plugin-grid 3.3.0 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +21 -1
  3. package/dist/index.js +631 -599
  4. package/dist/index.umd.cjs +8 -8
  5. package/package.json +44 -12
  6. package/.turbo/turbo-build.log +0 -32
  7. package/src/FormulaBar.tsx +0 -151
  8. package/src/GroupRow.tsx +0 -69
  9. package/src/ImportWizard.tsx +0 -412
  10. package/src/InlineEditing.tsx +0 -235
  11. package/src/ListColumnExtensions.test.tsx +0 -373
  12. package/src/ListColumnSchema.test.ts +0 -88
  13. package/src/ObjectGrid.EdgeCases.stories.tsx +0 -147
  14. package/src/ObjectGrid.msw.test.tsx +0 -130
  15. package/src/ObjectGrid.stories.tsx +0 -139
  16. package/src/ObjectGrid.tsx +0 -1598
  17. package/src/SplitPaneGrid.tsx +0 -120
  18. package/src/VirtualGrid.tsx +0 -183
  19. package/src/__tests__/GroupRow.test.tsx +0 -206
  20. package/src/__tests__/ImportPreview.test.tsx +0 -171
  21. package/src/__tests__/InlineEditing.test.tsx +0 -360
  22. package/src/__tests__/VirtualGrid.test.tsx +0 -438
  23. package/src/__tests__/accessibility.test.tsx +0 -254
  24. package/src/__tests__/accessorKey-inference.test.tsx +0 -132
  25. package/src/__tests__/airtable-style.test.tsx +0 -508
  26. package/src/__tests__/column-features.test.tsx +0 -490
  27. package/src/__tests__/grid-export.test.tsx +0 -121
  28. package/src/__tests__/mobile-card-view.test.tsx +0 -355
  29. package/src/__tests__/objectdef-enrichment.test.tsx +0 -566
  30. package/src/__tests__/performance-benchmark.test.tsx +0 -182
  31. package/src/__tests__/phase11-features.test.tsx +0 -418
  32. package/src/__tests__/row-bulk-actions.test.tsx +0 -413
  33. package/src/__tests__/row-height.test.tsx +0 -160
  34. package/src/__tests__/useGroupedData.test.ts +0 -165
  35. package/src/__tests__/view-states.test.tsx +0 -203
  36. package/src/components/BulkActionBar.tsx +0 -66
  37. package/src/components/RowActionMenu.tsx +0 -91
  38. package/src/index.test.tsx +0 -29
  39. package/src/index.tsx +0 -99
  40. package/src/useCellClipboard.ts +0 -136
  41. package/src/useColumnSummary.ts +0 -128
  42. package/src/useGradientColor.ts +0 -103
  43. package/src/useGroupReorder.ts +0 -123
  44. package/src/useGroupedData.ts +0 -187
  45. package/src/useRowColor.ts +0 -74
  46. package/tsconfig.json +0 -9
  47. package/vite.config.ts +0 -58
  48. package/vitest.config.ts +0 -13
  49. package/vitest.setup.ts +0 -1
@@ -1,360 +0,0 @@
1
- /**
2
- * InlineEditing Component Tests
3
- *
4
- * Tests for the grid inline editing component covering save/cancel,
5
- * validation feedback, keyboard navigation, and display modes.
6
- */
7
-
8
- import { describe, it, expect, vi } from 'vitest';
9
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
10
- import userEvent from '@testing-library/user-event';
11
- import '@testing-library/jest-dom';
12
- import { InlineEditing } from '../InlineEditing';
13
-
14
- // Mock lucide-react icons
15
- vi.mock('lucide-react', () => ({
16
- Check: (props: any) => <svg data-testid="icon-check" {...props} />,
17
- X: (props: any) => <svg data-testid="icon-x" {...props} />,
18
- }));
19
-
20
- // Mock cn utility to pass through classes
21
- vi.mock('@object-ui/components', () => ({
22
- cn: (...args: any[]) => args.filter(Boolean).join(' '),
23
- }));
24
-
25
- describe('InlineEditing', () => {
26
- describe('Display mode', () => {
27
- it('renders the value in display mode by default', () => {
28
- render(<InlineEditing value="Hello" onSave={vi.fn()} />);
29
-
30
- expect(screen.getByText('Hello')).toBeInTheDocument();
31
- // Should not show an input
32
- expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
33
- });
34
-
35
- it('shows placeholder when value is empty', () => {
36
- render(<InlineEditing value="" onSave={vi.fn()} placeholder="Enter text" />);
37
-
38
- expect(screen.getByText('Enter text')).toBeInTheDocument();
39
- });
40
-
41
- it('shows default placeholder when value is null', () => {
42
- render(<InlineEditing value={null} onSave={vi.fn()} />);
43
-
44
- expect(screen.getByText('Click to edit')).toBeInTheDocument();
45
- });
46
-
47
- it('has data-slot="inline-editing" on root', () => {
48
- const { container } = render(<InlineEditing value="test" onSave={vi.fn()} />);
49
- expect(container.querySelector('[data-slot="inline-editing"]')).toBeInTheDocument();
50
- });
51
-
52
- it('applies custom className', () => {
53
- const { container } = render(
54
- <InlineEditing value="test" onSave={vi.fn()} className="custom-class" />,
55
- );
56
- const root = container.querySelector('[data-slot="inline-editing"]');
57
- expect(root?.className).toContain('custom-class');
58
- });
59
- });
60
-
61
- describe('Entering edit mode', () => {
62
- it('enters edit mode on click', async () => {
63
- const user = userEvent.setup();
64
- render(<InlineEditing value="Hello" onSave={vi.fn()} />);
65
-
66
- await user.click(screen.getByRole('button'));
67
-
68
- expect(screen.getByRole('textbox')).toBeInTheDocument();
69
- expect((screen.getByRole('textbox') as HTMLInputElement).value).toBe('Hello');
70
- });
71
-
72
- it('enters edit mode on Enter key press', () => {
73
- render(<InlineEditing value="Hello" onSave={vi.fn()} />);
74
-
75
- fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });
76
-
77
- expect(screen.getByRole('textbox')).toBeInTheDocument();
78
- });
79
-
80
- it('enters edit mode on Space key press', () => {
81
- render(<InlineEditing value="Hello" onSave={vi.fn()} />);
82
-
83
- fireEvent.keyDown(screen.getByRole('button'), { key: ' ' });
84
-
85
- expect(screen.getByRole('textbox')).toBeInTheDocument();
86
- });
87
-
88
- it('starts in edit mode when editing prop is true', () => {
89
- render(<InlineEditing value="Hello" onSave={vi.fn()} editing={true} />);
90
-
91
- expect(screen.getByRole('textbox')).toBeInTheDocument();
92
- });
93
-
94
- it('does not enter edit mode when disabled', async () => {
95
- const user = userEvent.setup();
96
- render(<InlineEditing value="Hello" onSave={vi.fn()} disabled />);
97
-
98
- await user.click(screen.getByText('Hello'));
99
-
100
- expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
101
- });
102
- });
103
-
104
- describe('Save', () => {
105
- it('saves on Enter key press', async () => {
106
- const user = userEvent.setup();
107
- const onSave = vi.fn();
108
- render(<InlineEditing value="Hello" onSave={onSave} editing={true} />);
109
-
110
- const input = screen.getByRole('textbox');
111
- await user.clear(input);
112
- await user.type(input, 'World');
113
- await user.keyboard('{Enter}');
114
-
115
- await waitFor(() => {
116
- expect(onSave).toHaveBeenCalledWith('World');
117
- });
118
- });
119
-
120
- it('saves on save button click', async () => {
121
- const user = userEvent.setup();
122
- const onSave = vi.fn();
123
- render(<InlineEditing value="Hello" onSave={onSave} editing={true} />);
124
-
125
- const input = screen.getByRole('textbox');
126
- await user.clear(input);
127
- await user.type(input, 'Updated');
128
-
129
- await user.click(screen.getByLabelText('Save'));
130
-
131
- await waitFor(() => {
132
- expect(onSave).toHaveBeenCalledWith('Updated');
133
- });
134
- });
135
-
136
- it('coerces value to number when type is number', async () => {
137
- const user = userEvent.setup();
138
- const onSave = vi.fn();
139
- render(
140
- <InlineEditing value={42} onSave={onSave} type="number" editing={true} />,
141
- );
142
-
143
- const input = screen.getByRole('spinbutton');
144
- await user.clear(input);
145
- await user.type(input, '99');
146
- await user.click(screen.getByLabelText('Save'));
147
-
148
- await waitFor(() => {
149
- expect(onSave).toHaveBeenCalledWith(99);
150
- });
151
- });
152
-
153
- it('exits edit mode after successful save', async () => {
154
- const user = userEvent.setup();
155
- const onSave = vi.fn();
156
- render(<InlineEditing value="Hello" onSave={onSave} editing={true} />);
157
-
158
- await user.click(screen.getByLabelText('Save'));
159
-
160
- await waitFor(() => {
161
- expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
162
- });
163
- });
164
- });
165
-
166
- describe('Cancel', () => {
167
- it('cancels on Escape key press', async () => {
168
- const user = userEvent.setup();
169
- const onCancel = vi.fn();
170
- render(
171
- <InlineEditing value="Hello" onSave={vi.fn()} onCancel={onCancel} editing={true} />,
172
- );
173
-
174
- const input = screen.getByRole('textbox');
175
- await user.type(input, ' extra');
176
- await user.keyboard('{Escape}');
177
-
178
- expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
179
- expect(onCancel).toHaveBeenCalled();
180
- });
181
-
182
- it('cancels on cancel button click', async () => {
183
- const user = userEvent.setup();
184
- const onCancel = vi.fn();
185
- render(
186
- <InlineEditing value="Hello" onSave={vi.fn()} onCancel={onCancel} editing={true} />,
187
- );
188
-
189
- await user.click(screen.getByLabelText('Cancel'));
190
-
191
- expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
192
- expect(onCancel).toHaveBeenCalled();
193
- });
194
-
195
- it('restores original value after cancel', async () => {
196
- const user = userEvent.setup();
197
- render(
198
- <InlineEditing value="Original" onSave={vi.fn()} editing={true} />,
199
- );
200
-
201
- const input = screen.getByRole('textbox');
202
- await user.clear(input);
203
- await user.type(input, 'Changed');
204
- await user.keyboard('{Escape}');
205
-
206
- // Back to display mode, showing original value
207
- expect(screen.getByText('Original')).toBeInTheDocument();
208
- });
209
- });
210
-
211
- describe('Validation', () => {
212
- it('shows validation error and prevents save', async () => {
213
- const user = userEvent.setup();
214
- const onSave = vi.fn();
215
- const validate = (val: any) =>
216
- String(val).trim() === '' ? 'Required field' : undefined;
217
-
218
- render(
219
- <InlineEditing
220
- value="Hello"
221
- onSave={onSave}
222
- validate={validate}
223
- editing={true}
224
- />,
225
- );
226
-
227
- const input = screen.getByRole('textbox');
228
- await user.clear(input);
229
- await user.click(screen.getByLabelText('Save'));
230
-
231
- expect(screen.getByRole('alert')).toHaveTextContent('Required field');
232
- expect(onSave).not.toHaveBeenCalled();
233
- // Should still be in edit mode
234
- expect(screen.getByRole('textbox')).toBeInTheDocument();
235
- });
236
-
237
- it('clears validation error on input change', async () => {
238
- const user = userEvent.setup();
239
- const validate = (val: any) =>
240
- String(val).trim() === '' ? 'Required field' : undefined;
241
-
242
- render(
243
- <InlineEditing
244
- value="Hello"
245
- onSave={vi.fn()}
246
- validate={validate}
247
- editing={true}
248
- />,
249
- );
250
-
251
- const input = screen.getByRole('textbox');
252
- await user.clear(input);
253
- await user.click(screen.getByLabelText('Save'));
254
-
255
- expect(screen.getByRole('alert')).toBeInTheDocument();
256
-
257
- // Start typing to clear error
258
- await user.type(input, 'A');
259
- expect(screen.queryByRole('alert')).not.toBeInTheDocument();
260
- });
261
-
262
- it('shows error returned from onSave', async () => {
263
- const user = userEvent.setup();
264
- const onSave = vi.fn().mockReturnValue('Server error');
265
-
266
- render(
267
- <InlineEditing value="Hello" onSave={onSave} editing={true} />,
268
- );
269
-
270
- await user.click(screen.getByLabelText('Save'));
271
-
272
- await waitFor(() => {
273
- expect(screen.getByRole('alert')).toHaveTextContent('Server error');
274
- });
275
- });
276
-
277
- it('marks input as aria-invalid when error is present', async () => {
278
- const user = userEvent.setup();
279
- const validate = () => 'Error';
280
-
281
- render(
282
- <InlineEditing value="" onSave={vi.fn()} validate={validate} editing={true} />,
283
- );
284
-
285
- await user.click(screen.getByLabelText('Save'));
286
-
287
- expect(screen.getByRole('textbox')).toHaveAttribute('aria-invalid', 'true');
288
- });
289
- });
290
-
291
- describe('Async save', () => {
292
- it('handles async onSave', async () => {
293
- const user = userEvent.setup();
294
- const onSave = vi.fn().mockResolvedValue(undefined);
295
-
296
- render(<InlineEditing value="Hello" onSave={onSave} editing={true} />);
297
-
298
- await user.click(screen.getByLabelText('Save'));
299
-
300
- await waitFor(() => {
301
- expect(onSave).toHaveBeenCalledWith('Hello');
302
- expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
303
- });
304
- });
305
-
306
- it('shows error when async onSave throws', async () => {
307
- const user = userEvent.setup();
308
- const onSave = vi.fn().mockRejectedValue(new Error('Network error'));
309
-
310
- render(<InlineEditing value="Hello" onSave={onSave} editing={true} />);
311
-
312
- await user.click(screen.getByLabelText('Save'));
313
-
314
- await waitFor(() => {
315
- expect(screen.getByRole('alert')).toHaveTextContent('Network error');
316
- });
317
-
318
- // Should still be in edit mode
319
- expect(screen.getByRole('textbox')).toBeInTheDocument();
320
- });
321
- });
322
-
323
- describe('Input types', () => {
324
- it('renders text input by default', () => {
325
- render(<InlineEditing value="text" onSave={vi.fn()} editing={true} />);
326
- expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text');
327
- });
328
-
329
- it('renders number input when type is number', () => {
330
- render(<InlineEditing value={42} onSave={vi.fn()} type="number" editing={true} />);
331
- expect(screen.getByRole('spinbutton')).toHaveAttribute('type', 'number');
332
- });
333
-
334
- it('renders email input when type is email', () => {
335
- render(<InlineEditing value="a@b.com" onSave={vi.fn()} type="email" editing={true} />);
336
- const input = screen.getByDisplayValue('a@b.com');
337
- expect(input).toHaveAttribute('type', 'email');
338
- });
339
- });
340
-
341
- describe('data-slot attributes', () => {
342
- it('has correct data-slot attributes in display mode', () => {
343
- const { container } = render(<InlineEditing value="test" onSave={vi.fn()} />);
344
-
345
- expect(container.querySelector('[data-slot="inline-editing"]')).toBeInTheDocument();
346
- expect(container.querySelector('[data-slot="inline-editing-display"]')).toBeInTheDocument();
347
- });
348
-
349
- it('has correct data-slot attributes in edit mode', () => {
350
- const { container } = render(
351
- <InlineEditing value="test" onSave={vi.fn()} editing={true} />,
352
- );
353
-
354
- expect(container.querySelector('[data-slot="inline-editing"]')).toBeInTheDocument();
355
- expect(container.querySelector('[data-slot="inline-editing-input"]')).toBeInTheDocument();
356
- expect(container.querySelector('[data-slot="inline-editing-save"]')).toBeInTheDocument();
357
- expect(container.querySelector('[data-slot="inline-editing-cancel"]')).toBeInTheDocument();
358
- });
359
- });
360
- });