Skip to content

Commit d40d750

Browse files
committed
Add support for additional view types in ListView and implement persistence tests
1 parent 64a1bec commit d40d750

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

packages/plugin-list/src/ListView.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,20 +179,47 @@ export const ListView: React.FC<ListViewProps> = ({
179179

180180
// Available view types based on schema configuration
181181
const availableViews = React.useMemo(() => {
182-
const views: ViewType[] = ['grid', 'list'];
182+
const views: ViewType[] = ['grid', 'list', 'spreadsheet'];
183183

184+
// Check for Kanban capabilities
184185
if (schema.options?.kanban?.groupField) {
185186
views.push('kanban');
186187
}
188+
189+
// Check for Calendar capabilities
187190
if (schema.options?.calendar?.startDateField) {
188191
views.push('calendar');
189192
}
193+
194+
// Check for Timeline capabilities
195+
if (schema.options?.timeline?.dateField || schema.options?.calendar?.startDateField) {
196+
views.push('timeline');
197+
}
198+
199+
// Check for Gantt capabilities
200+
if (schema.options?.gantt?.startDateField) {
201+
views.push('gantt');
202+
}
203+
204+
// Check for Map capabilities
205+
if (schema.options?.map?.locationField) {
206+
views.push('map');
207+
}
208+
209+
// Check for Chart capabilities
190210
if (schema.options?.chart) {
191211
views.push('chart');
192212
}
213+
214+
// Always allow switching back to the viewType defined in schema if it's one of the supported types
215+
// This ensures that if a view is configured as "map", the map button is shown even if we missed the options check above
216+
if (schema.viewType && !views.includes(schema.viewType as ViewType) &&
217+
['grid', 'list', 'kanban', 'calendar', 'spreadsheet', 'timeline', 'gantt', 'map', 'chart', 'gallery'].includes(schema.viewType)) {
218+
views.push(schema.viewType as ViewType);
219+
}
193220

194221
return views;
195-
}, [schema.options]);
222+
}, [schema.options, schema.viewType]);
196223

197224
return (
198225
<div className={cn('flex flex-col gap-4', className)}>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
};
53+
54+
renderWithProvider(<ListView schema={schema} />);
55+
56+
// Simulate changing to list view
57+
const listButton = screen.getByLabelText('List');
58+
fireEvent.click(listButton);
59+
60+
// Check scoped storage key
61+
const expectedKey = 'listview-tasks-my-custom-view-view';
62+
expect(localStorageMock.getItem(expectedKey)).toBe('list');
63+
64+
// Check fallback key is NOT set
65+
expect(localStorageMock.getItem('listview-tasks-view')).toBeNull();
66+
});
67+
68+
it('should not conflict with other views of the same object', () => {
69+
// Setup: View A (Global/Default) prefers Grid
70+
localStorageMock.setItem('listview-tasks-view', 'grid');
71+
72+
// Setup: View B (Special) prefers List
73+
// We define View B with valid options for Kanban to force it to render the button just in case,
74+
// but we will test switching between Grid/List.
75+
76+
const viewB_Schema: ListViewSchema = {
77+
type: 'list-view',
78+
id: 'special-view',
79+
objectName: 'tasks',
80+
viewType: 'list' // Default to List
81+
};
82+
83+
renderWithProvider(<ListView schema={viewB_Schema} />);
84+
85+
// Should use the schema default 'list' (since no storage exists for THIS view id)
86+
// It should NOT use 'grid' from the global/default view.
87+
88+
const listButton = screen.getByLabelText('List');
89+
expect(listButton.getAttribute('data-state')).toBe('on');
90+
91+
const gridButton = screen.getByLabelText('Grid');
92+
expect(gridButton.getAttribute('data-state')).toBe('off');
93+
});
94+
95+
it('should switch correctly when storage has a value for THIS view', () => {
96+
// Setup: This specific view was previously set to 'list'
97+
localStorageMock.setItem('listview-tasks-my-board-view', 'list');
98+
99+
const schema: ListViewSchema = {
100+
type: 'list-view',
101+
id: 'my-board',
102+
objectName: 'tasks',
103+
viewType: 'grid' // Default in schema is grid
104+
};
105+
106+
renderWithProvider(<ListView schema={schema} />);
107+
108+
// Should respect storage ('list') over schema ('grid')
109+
const listButton = screen.getByLabelText('List');
110+
expect(listButton.getAttribute('data-state')).toBe('on');
111+
});
112+
});

0 commit comments

Comments
 (0)