@object-ui/plugin-list 3.1.5 → 3.3.1

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 (52) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +21 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +30492 -38346
  5. package/dist/index.umd.cjs +30 -38
  6. package/dist/{src → packages/plugin-list/src}/ListView.d.ts +17 -1
  7. package/dist/packages/plugin-list/src/ListView.d.ts.map +1 -0
  8. package/dist/packages/plugin-list/src/ListView.stories.d.ts.map +1 -0
  9. package/dist/packages/plugin-list/src/ObjectGallery.d.ts.map +1 -0
  10. package/dist/packages/plugin-list/src/UserFilters.d.ts.map +1 -0
  11. package/dist/packages/plugin-list/src/ViewSwitcher.d.ts.map +1 -0
  12. package/dist/packages/plugin-list/src/components/TabBar.d.ts.map +1 -0
  13. package/dist/{src → packages/plugin-list/src}/index.d.ts +1 -1
  14. package/dist/packages/plugin-list/src/index.d.ts.map +1 -0
  15. package/dist/plugin-list.css +1 -2
  16. package/package.json +35 -13
  17. package/.turbo/turbo-build.log +0 -24
  18. package/dist/src/ListView.d.ts.map +0 -1
  19. package/dist/src/ListView.stories.d.ts.map +0 -1
  20. package/dist/src/ObjectGallery.d.ts.map +0 -1
  21. package/dist/src/UserFilters.d.ts.map +0 -1
  22. package/dist/src/ViewSwitcher.d.ts.map +0 -1
  23. package/dist/src/components/TabBar.d.ts.map +0 -1
  24. package/dist/src/index.d.ts.map +0 -1
  25. package/src/ListView.stories.tsx +0 -64
  26. package/src/ListView.tsx +0 -1688
  27. package/src/ObjectGallery.tsx +0 -308
  28. package/src/UserFilters.tsx +0 -453
  29. package/src/ViewSwitcher.tsx +0 -113
  30. package/src/__tests__/ConditionalFormatting.test.ts +0 -285
  31. package/src/__tests__/DataFetch.test.tsx +0 -253
  32. package/src/__tests__/Export.test.tsx +0 -175
  33. package/src/__tests__/FilterNormalization.test.ts +0 -162
  34. package/src/__tests__/GalleryGrouping.test.tsx +0 -237
  35. package/src/__tests__/GalleryTimelineSpecConfig.test.tsx +0 -203
  36. package/src/__tests__/ListView.test.tsx +0 -2151
  37. package/src/__tests__/ListViewGroupingPropagation.test.tsx +0 -250
  38. package/src/__tests__/ListViewPersistence.test.tsx +0 -129
  39. package/src/__tests__/ObjectGallery.test.tsx +0 -208
  40. package/src/__tests__/TabBar.test.tsx +0 -199
  41. package/src/__tests__/UserFilters.test.tsx +0 -486
  42. package/src/components/TabBar.tsx +0 -120
  43. package/src/index.tsx +0 -78
  44. package/tsconfig.json +0 -18
  45. package/vite.config.ts +0 -56
  46. package/vitest.config.ts +0 -12
  47. package/vitest.setup.ts +0 -1
  48. /package/dist/{src → packages/plugin-list/src}/ListView.stories.d.ts +0 -0
  49. /package/dist/{src → packages/plugin-list/src}/ObjectGallery.d.ts +0 -0
  50. /package/dist/{src → packages/plugin-list/src}/UserFilters.d.ts +0 -0
  51. /package/dist/{src → packages/plugin-list/src}/ViewSwitcher.d.ts +0 -0
  52. /package/dist/{src → packages/plugin-list/src}/components/TabBar.d.ts +0 -0
@@ -1,250 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { describe, it, expect, vi, beforeEach } from 'vitest';
10
- import { render, screen, fireEvent } from '@testing-library/react';
11
- import React from 'react';
12
-
13
- // Capture the schema prop passed to SchemaRenderer
14
- let capturedSchema: any = null;
15
-
16
- vi.mock('@object-ui/react', () => ({
17
- SchemaRenderer: (props: any) => {
18
- capturedSchema = props.schema;
19
- return <div data-testid="schema-renderer" data-schema-type={props.schema?.type}>{props.schema?.type}</div>;
20
- },
21
- useNavigationOverlay: () => ({
22
- isOverlay: false,
23
- handleClick: vi.fn(),
24
- selectedRecord: null,
25
- isOpen: false,
26
- close: vi.fn(),
27
- setIsOpen: vi.fn(),
28
- mode: 'page' as const,
29
- width: undefined,
30
- view: undefined,
31
- open: vi.fn(),
32
- }),
33
- useDensityMode: () => ['comfortable', vi.fn()] as const,
34
- SchemaRendererProvider: ({ children }: any) => <div>{children}</div>,
35
- }));
36
-
37
- vi.mock('@object-ui/components', () => ({
38
- cn: (...args: any[]) => args.filter(Boolean).join(' '),
39
- Input: (props: any) => <input {...props} />,
40
- Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
41
- Badge: ({ children, ...props }: any) => <span {...props}>{children}</span>,
42
- Popover: ({ children }: any) => <div>{children}</div>,
43
- PopoverTrigger: ({ children, ...props }: any) => <div {...props}>{children}</div>,
44
- PopoverContent: ({ children }: any) => <div>{children}</div>,
45
- FilterBuilder: () => null,
46
- SortBuilder: () => null,
47
- NavigationOverlay: () => null,
48
- }));
49
-
50
- vi.mock('@object-ui/mobile', () => ({
51
- usePullToRefresh: () => ({ pullRef: { current: null } }),
52
- }));
53
-
54
- vi.mock('@object-ui/core', async (importOriginal) => {
55
- const actual = await importOriginal<Record<string, unknown>>();
56
- return {
57
- ...actual,
58
- ExpressionEvaluator: {
59
- evaluate: vi.fn((expr: string) => expr),
60
- },
61
- };
62
- });
63
-
64
- vi.mock('@object-ui/i18n', () => ({
65
- useObjectTranslation: () => ({
66
- t: (key: string, fallback?: string) => fallback || key,
67
- }),
68
- }));
69
-
70
- vi.mock('../ViewSwitcher', () => ({
71
- ViewSwitcher: ({ currentView, onViewChange }: any) => (
72
- <div data-testid="view-switcher">
73
- <button aria-label="Grid" onClick={() => onViewChange('grid')}>Grid</button>
74
- <button aria-label="Kanban" onClick={() => onViewChange('kanban')}>Kanban</button>
75
- <button aria-label="Gallery" onClick={() => onViewChange('gallery')}>Gallery</button>
76
- </div>
77
- ),
78
- ViewType: {},
79
- }));
80
-
81
- vi.mock('../UserFilters', () => ({
82
- UserFilters: () => null,
83
- }));
84
-
85
- vi.mock('lucide-react', () => ({
86
- Search: () => <span>Search</span>,
87
- SlidersHorizontal: () => <span>Sliders</span>,
88
- ArrowUpDown: () => <span>Sort</span>,
89
- X: () => <span>X</span>,
90
- EyeOff: () => <span>EyeOff</span>,
91
- Group: () => <span>Group</span>,
92
- Paintbrush: () => <span>Color</span>,
93
- Ruler: () => <span>Ruler</span>,
94
- Inbox: () => <span>Inbox</span>,
95
- Download: () => <span>Download</span>,
96
- AlignJustify: () => <span>Density</span>,
97
- Share2: () => <span>Share</span>,
98
- Printer: () => <span>Print</span>,
99
- Plus: () => <span>Plus</span>,
100
- icons: {},
101
- }));
102
-
103
- import { ListView } from '../ListView';
104
-
105
- const groupingConfig = {
106
- fields: [{ field: 'category', order: 'asc' as const, collapsed: false }],
107
- };
108
-
109
- const testData = [
110
- { id: '1', name: 'Product A', category: 'Electronics' },
111
- { id: '2', name: 'Product B', category: 'Tools' },
112
- ];
113
-
114
- describe('ListView grouping config propagation', () => {
115
- beforeEach(() => {
116
- capturedSchema = null;
117
- });
118
-
119
- it('passes grouping config to grid view schema', () => {
120
- render(
121
- <ListView
122
- schema={{
123
- type: 'list-view',
124
- objectName: 'products',
125
- viewType: 'grid',
126
- fields: ['name', 'category'],
127
- grouping: groupingConfig,
128
- data: testData,
129
- }}
130
- />,
131
- );
132
-
133
- expect(capturedSchema).toBeDefined();
134
- expect(capturedSchema.type).toBe('object-grid');
135
- expect(capturedSchema.grouping).toEqual(groupingConfig);
136
- });
137
-
138
- it('passes grouping config to kanban view schema', () => {
139
- render(
140
- <ListView
141
- schema={{
142
- type: 'list-view',
143
- objectName: 'products',
144
- viewType: 'kanban',
145
- fields: ['name', 'category'],
146
- grouping: groupingConfig,
147
- data: testData,
148
- kanban: { groupField: 'status' },
149
- }}
150
- showViewSwitcher={true}
151
- />,
152
- );
153
-
154
- // Switch to kanban view
155
- const kanbanBtn = screen.getByLabelText('Kanban');
156
- fireEvent.click(kanbanBtn);
157
-
158
- expect(capturedSchema).toBeDefined();
159
- expect(capturedSchema.type).toBe('object-kanban');
160
- expect(capturedSchema.grouping).toEqual(groupingConfig);
161
- });
162
-
163
- it('passes grouping config to gallery view schema', () => {
164
- render(
165
- <ListView
166
- schema={{
167
- type: 'list-view',
168
- objectName: 'products',
169
- viewType: 'gallery',
170
- fields: ['name', 'category'],
171
- grouping: groupingConfig,
172
- data: testData,
173
- }}
174
- showViewSwitcher={true}
175
- />,
176
- );
177
-
178
- // Switch to gallery view
179
- const galleryBtn = screen.getByLabelText('Gallery');
180
- fireEvent.click(galleryBtn);
181
-
182
- expect(capturedSchema).toBeDefined();
183
- expect(capturedSchema.type).toBe('object-gallery');
184
- expect(capturedSchema.grouping).toEqual(groupingConfig);
185
- });
186
-
187
- it('updates grid schema when user toggles grouping via toolbar', async () => {
188
- render(
189
- <ListView
190
- schema={{
191
- type: 'list-view',
192
- objectName: 'products',
193
- viewType: 'grid',
194
- fields: ['name', 'category'],
195
- data: testData,
196
- }}
197
- />,
198
- );
199
-
200
- // Initially no grouping
201
- expect(capturedSchema).toBeDefined();
202
- expect(capturedSchema.type).toBe('object-grid');
203
- expect(capturedSchema.grouping).toBeUndefined();
204
-
205
- // Open Group popover
206
- const groupButton = screen.getByRole('button', { name: /group/i });
207
- fireEvent.click(groupButton);
208
-
209
- // Select the 'category' field checkbox in the group field list
210
- const fieldList = screen.getByTestId('group-field-list');
211
- const checkboxes = fieldList.querySelectorAll('input[type="checkbox"]');
212
- // Find the checkbox for 'category'
213
- const categoryCheckbox = Array.from(checkboxes).find(cb => {
214
- const label = cb.closest('label');
215
- return label?.textContent?.includes('category');
216
- });
217
- expect(categoryCheckbox).toBeDefined();
218
- fireEvent.click(categoryCheckbox!);
219
-
220
- // Schema should now include grouping config
221
- expect(capturedSchema.grouping).toBeDefined();
222
- expect(capturedSchema.grouping.fields).toEqual(
223
- expect.arrayContaining([
224
- expect.objectContaining({ field: 'category' }),
225
- ]),
226
- );
227
-
228
- // Toggle off — click the checkbox again
229
- fireEvent.click(categoryCheckbox!);
230
- expect(capturedSchema.grouping).toBeUndefined();
231
- });
232
-
233
- it('does not pass grouping when no grouping config exists', () => {
234
- render(
235
- <ListView
236
- schema={{
237
- type: 'list-view',
238
- objectName: 'products',
239
- viewType: 'grid',
240
- fields: ['name', 'category'],
241
- data: testData,
242
- }}
243
- />,
244
- );
245
-
246
- expect(capturedSchema).toBeDefined();
247
- expect(capturedSchema.type).toBe('object-grid');
248
- expect(capturedSchema.grouping).toBeUndefined();
249
- });
250
- });
@@ -1,129 +0,0 @@
1
- /**
2
- * ObjectUI -- Persistence Tests
3
- */
4
-
5
- import { describe, it, expect, vi, beforeEach } from 'vitest';
6
- import { render, screen, fireEvent } from '@testing-library/react';
7
- import { ListView } from '../ListView';
8
- import type { ListViewSchema } from '@object-ui/types';
9
- import { SchemaRendererProvider } from '@object-ui/react';
10
-
11
- // Mock localStorage
12
- const localStorageMock = (() => {
13
- let store: Record<string, string> = {};
14
- return {
15
- getItem: (key: string) => store[key] || null,
16
- setItem: (key: string, value: string) => { store[key] = value; },
17
- clear: () => { store = {}; },
18
- removeItem: (key: string) => { delete store[key]; },
19
- };
20
- })();
21
-
22
- const mockDataSource = {
23
- find: vi.fn().mockResolvedValue([]),
24
- findOne: vi.fn(),
25
- create: vi.fn(),
26
- update: vi.fn(),
27
- delete: vi.fn(),
28
- };
29
-
30
- const renderWithProvider = (component: React.ReactNode) => {
31
- return render(
32
- <SchemaRendererProvider dataSource={mockDataSource}>
33
- {component}
34
- </SchemaRendererProvider>
35
- );
36
- };
37
-
38
- Object.defineProperty(window, 'localStorage', { value: localStorageMock });
39
-
40
- describe('ListView Persistence', () => {
41
- beforeEach(() => {
42
- localStorageMock.clear();
43
- vi.clearAllMocks();
44
- });
45
-
46
- it('should use unique storage key when schema.id is provided', () => {
47
- const schema: ListViewSchema = {
48
- type: 'list-view',
49
- id: 'my-custom-view',
50
- objectName: 'tasks',
51
- viewType: 'grid', // Start with grid
52
- options: {
53
- kanban: {
54
- groupField: 'status',
55
- },
56
- },
57
- };
58
-
59
- renderWithProvider(<ListView schema={schema} showViewSwitcher={true} />);
60
-
61
- // Simulate changing to kanban view
62
- const kanbanButton = screen.getByLabelText('Kanban');
63
- fireEvent.click(kanbanButton);
64
-
65
- // Check scoped storage key
66
- const expectedKey = 'listview-tasks-my-custom-view-view';
67
- expect(localStorageMock.getItem(expectedKey)).toBe('kanban');
68
-
69
- // Check fallback key is NOT set
70
- expect(localStorageMock.getItem('listview-tasks-view')).toBeNull();
71
- });
72
-
73
- it('should not conflict with other views of the same object', () => {
74
- // Setup: View A (Global/Default) prefers Grid
75
- localStorageMock.setItem('listview-tasks-view', 'grid');
76
-
77
- // Setup: View B (Special) prefers Kanban
78
- // We define View B with valid options for Kanban to force it to render the button
79
-
80
- const viewB_Schema: ListViewSchema = {
81
- type: 'list-view',
82
- id: 'special-view',
83
- objectName: 'tasks',
84
- viewType: 'kanban', // Default to Kanban
85
- options: {
86
- kanban: {
87
- groupField: 'status',
88
- },
89
- },
90
- };
91
-
92
- renderWithProvider(<ListView schema={viewB_Schema} showViewSwitcher={true} />);
93
-
94
- // Should use the schema default 'kanban' (since no storage exists for THIS view id)
95
- // It should NOT use 'grid' from the global/default view.
96
-
97
- const kanbanButton = screen.getByLabelText('Kanban');
98
- expect(kanbanButton.getAttribute('data-state')).toBe('on');
99
-
100
- const gridButton = screen.getByLabelText('Grid');
101
- expect(gridButton.getAttribute('data-state')).toBe('off');
102
- });
103
-
104
- it('should switch correctly when storage has a value for THIS view', () => {
105
- // Setup: This specific view was previously set to 'kanban'
106
- localStorageMock.setItem('listview-tasks-my-board-view', 'kanban');
107
-
108
- const schema: ListViewSchema = {
109
- type: 'list-view',
110
- id: 'my-board',
111
- objectName: 'tasks',
112
- viewType: 'grid', // Default in schema is grid
113
- options: {
114
- kanban: {
115
- groupField: 'status',
116
- },
117
- },
118
- };
119
-
120
- renderWithProvider(<ListView schema={schema} showViewSwitcher={true} />);
121
-
122
- // Should respect schema ('grid') because storage persistence is currently disabled
123
- const kanbanButton = screen.getByLabelText('Kanban');
124
- expect(kanbanButton.getAttribute('data-state')).toBe('off');
125
-
126
- const gridButton = screen.getByLabelText('Grid');
127
- expect(gridButton.getAttribute('data-state')).toBe('on');
128
- });
129
- });
@@ -1,208 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { describe, it, expect, vi } from 'vitest';
10
- import { render, screen, fireEvent } from '@testing-library/react';
11
- import { ObjectGallery } from '../ObjectGallery';
12
-
13
- // Mock useDataScope, useSchemaContext, and useNavigationOverlay
14
- const mockHandleClick = vi.fn();
15
- const mockNavigationOverlay = {
16
- isOverlay: false,
17
- handleClick: mockHandleClick,
18
- selectedRecord: null,
19
- isOpen: false,
20
- close: vi.fn(),
21
- setIsOpen: vi.fn(),
22
- mode: 'page' as const,
23
- width: undefined,
24
- view: undefined,
25
- open: vi.fn(),
26
- };
27
-
28
- vi.mock('@object-ui/react', () => {
29
- const React = require('react');
30
- return {
31
- useDataScope: () => undefined,
32
- SchemaRendererContext: React.createContext(null),
33
- useNavigationOverlay: () => mockNavigationOverlay,
34
- };
35
- });
36
-
37
- vi.mock('@object-ui/components', () => ({
38
- cn: (...args: any[]) => args.filter(Boolean).join(' '),
39
- Card: ({ children, onClick, ...props }: any) => (
40
- <div data-testid="gallery-card" onClick={onClick} {...props}>{children}</div>
41
- ),
42
- CardContent: ({ children, ...props }: any) => <div {...props}>{children}</div>,
43
- NavigationOverlay: ({ children, selectedRecord }: any) => (
44
- selectedRecord ? <div data-testid="navigation-overlay">{children(selectedRecord)}</div> : null
45
- ),
46
- }));
47
-
48
- vi.mock('@object-ui/core', () => ({
49
- ComponentRegistry: { register: vi.fn() },
50
- }));
51
-
52
- const mockItems = [
53
- { id: '1', name: 'Item 1', image: 'https://example.com/1.jpg' },
54
- { id: '2', name: 'Item 2', image: 'https://example.com/2.jpg' },
55
- ];
56
-
57
- describe('ObjectGallery', () => {
58
- it('renders gallery items', () => {
59
- const schema = { objectName: 'products' };
60
- render(<ObjectGallery schema={schema} data={mockItems} />);
61
- expect(screen.getByText('Item 1')).toBeInTheDocument();
62
- expect(screen.getByText('Item 2')).toBeInTheDocument();
63
- });
64
-
65
- it('calls navigation.handleClick on card click', () => {
66
- const schema = {
67
- objectName: 'products',
68
- navigation: { mode: 'drawer' as const },
69
- };
70
- render(<ObjectGallery schema={schema} data={mockItems} />);
71
-
72
- const cards = screen.getAllByTestId('gallery-card');
73
- fireEvent.click(cards[0]);
74
-
75
- expect(mockHandleClick).toHaveBeenCalledWith(mockItems[0]);
76
- });
77
-
78
- it('renders with cursor-pointer when navigation is configured', () => {
79
- const schema = {
80
- objectName: 'products',
81
- navigation: { mode: 'drawer' as const },
82
- };
83
- render(<ObjectGallery schema={schema} data={mockItems} />);
84
-
85
- const cards = screen.getAllByTestId('gallery-card');
86
- expect(cards.length).toBe(2);
87
- });
88
-
89
- it('renders with cursor-pointer when onCardClick is provided', () => {
90
- const onCardClick = vi.fn();
91
- const schema = { objectName: 'products' };
92
- render(<ObjectGallery schema={schema} data={mockItems} onCardClick={onCardClick} />);
93
-
94
- const cards = screen.getAllByTestId('gallery-card');
95
- fireEvent.click(cards[0]);
96
-
97
- expect(mockHandleClick).toHaveBeenCalled();
98
- });
99
-
100
- // ============================
101
- // Spec GalleryConfig integration
102
- // ============================
103
- describe('Spec GalleryConfig', () => {
104
- it('schema.gallery.coverField drives cover image', () => {
105
- const data = [
106
- { id: '1', name: 'Photo A', photo: 'https://example.com/a.jpg' },
107
- ];
108
- const schema = {
109
- objectName: 'albums',
110
- gallery: { coverField: 'photo' },
111
- };
112
- render(<ObjectGallery schema={schema} data={data} />);
113
- const img = screen.getByRole('img');
114
- expect(img).toHaveAttribute('src', 'https://example.com/a.jpg');
115
- });
116
-
117
- it('schema.gallery.cardSize controls grid layout class', () => {
118
- const data = [{ id: '1', name: 'Item', image: 'https://example.com/1.jpg' }];
119
-
120
- // small cards → more columns
121
- const { container: c1 } = render(
122
- <ObjectGallery schema={{ objectName: 'a', gallery: { cardSize: 'small' } }} data={data} />,
123
- );
124
- expect(c1.querySelector('[role="list"]')?.className).toContain('grid-cols-2');
125
-
126
- // large cards → fewer columns
127
- const { container: c2 } = render(
128
- <ObjectGallery schema={{ objectName: 'a', gallery: { cardSize: 'large' } }} data={data} />,
129
- );
130
- expect(c2.querySelector('[role="list"]')?.className).toContain('lg:grid-cols-3');
131
- });
132
-
133
- it('schema.gallery.coverFit applies object-contain class', () => {
134
- const data = [{ id: '1', name: 'Item', thumb: 'https://example.com/1.jpg' }];
135
- const schema = {
136
- objectName: 'items',
137
- gallery: { coverField: 'thumb', coverFit: 'contain' as const },
138
- };
139
- render(<ObjectGallery schema={schema} data={data} />);
140
- const img = screen.getByRole('img');
141
- expect(img.className).toContain('object-contain');
142
- });
143
-
144
- it('schema.gallery.visibleFields shows additional fields on card', () => {
145
- const data = [
146
- { id: '1', name: 'Item 1', status: 'active', category: 'books' },
147
- ];
148
- const schema = {
149
- objectName: 'items',
150
- gallery: { visibleFields: ['status', 'category'] },
151
- };
152
- render(<ObjectGallery schema={schema} data={data} />);
153
- expect(screen.getByText('active')).toBeInTheDocument();
154
- expect(screen.getByText('books')).toBeInTheDocument();
155
- });
156
-
157
- it('schema.gallery.titleField overrides default title', () => {
158
- const data = [
159
- { id: '1', name: 'Default Name', displayName: 'Custom Title' },
160
- ];
161
- const schema = {
162
- objectName: 'items',
163
- gallery: { titleField: 'displayName' },
164
- };
165
- render(<ObjectGallery schema={schema} data={data} />);
166
- expect(screen.getByText('Custom Title')).toBeInTheDocument();
167
- });
168
-
169
- it('falls back to legacy imageField when gallery.coverField is not set', () => {
170
- const data = [
171
- { id: '1', name: 'Item', legacyImg: 'https://example.com/legacy.jpg' },
172
- ];
173
- const schema = {
174
- objectName: 'items',
175
- imageField: 'legacyImg',
176
- };
177
- render(<ObjectGallery schema={schema} data={data} />);
178
- const img = screen.getByRole('img');
179
- expect(img).toHaveAttribute('src', 'https://example.com/legacy.jpg');
180
- });
181
-
182
- it('falls back to legacy titleField when gallery.titleField is not set', () => {
183
- const data = [
184
- { id: '1', name: 'Default', label: 'Legacy Title' },
185
- ];
186
- const schema = {
187
- objectName: 'items',
188
- titleField: 'label',
189
- };
190
- render(<ObjectGallery schema={schema} data={data} />);
191
- expect(screen.getByText('Legacy Title')).toBeInTheDocument();
192
- });
193
-
194
- it('spec gallery.coverField takes priority over legacy imageField', () => {
195
- const data = [
196
- { id: '1', name: 'Item', photo: 'https://spec.com/a.jpg', oldImg: 'https://old.com/b.jpg' },
197
- ];
198
- const schema = {
199
- objectName: 'items',
200
- imageField: 'oldImg',
201
- gallery: { coverField: 'photo' },
202
- };
203
- render(<ObjectGallery schema={schema} data={data} />);
204
- const img = screen.getByRole('img');
205
- expect(img).toHaveAttribute('src', 'https://spec.com/a.jpg');
206
- });
207
- });
208
- });