@object-ui/plugin-calendar 0.3.1 → 2.0.0

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.
@@ -0,0 +1,100 @@
1
+ import { describe, it, expect, vi, beforeAll, afterAll, afterEach } from 'vitest';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { ObjectCalendar } from './ObjectCalendar';
5
+ import { ObjectStackAdapter } from '@object-ui/data-objectstack';
6
+ import { setupServer } from 'msw/node';
7
+ import { http, HttpResponse } from 'msw';
8
+ import React from 'react';
9
+
10
+ const BASE_URL = 'http://localhost';
11
+
12
+ // --- Mock Data ---
13
+
14
+ // Create a stable date at noon to avoid day-spanning issues when tests run late at night
15
+ const todayAtNoon = new Date();
16
+ todayAtNoon.setHours(12, 0, 0, 0);
17
+
18
+ const mockEvents = {
19
+ value: [
20
+ {
21
+ _id: '1',
22
+ title: 'Meeting with Client',
23
+ start: todayAtNoon.toISOString(),
24
+ end: new Date(todayAtNoon.getTime() + 3600000).toISOString(),
25
+ type: 'business'
26
+ },
27
+ {
28
+ _id: '2',
29
+ title: 'Team Lunch',
30
+ start: new Date(todayAtNoon.getTime() + 86400000).toISOString(), // Tomorrow
31
+ type: 'personal'
32
+ }
33
+ ]
34
+ };
35
+
36
+ // --- MSW Setup ---
37
+
38
+ const handlers = [
39
+ http.options('*', () => {
40
+ return new HttpResponse(null, { status: 200 });
41
+ }),
42
+
43
+ http.get(`${BASE_URL}/api/v1`, () => {
44
+ return HttpResponse.json({ status: 'ok', version: '1.0.0' });
45
+ }),
46
+
47
+ // Data Query: GET /api/v1/data/events
48
+ http.get(`${BASE_URL}/api/v1/data/events`, () => {
49
+ return HttpResponse.json(mockEvents);
50
+ }),
51
+
52
+ // Metadata Query
53
+ http.get(`${BASE_URL}/api/v1/metadata/object/events`, () => {
54
+ return HttpResponse.json({ fields: {} });
55
+ })
56
+ ];
57
+
58
+ const server = setupServer(...handlers);
59
+
60
+ // --- Test Suite ---
61
+
62
+ describe('ObjectCalendar with MSW', () => {
63
+ beforeAll(() => server.listen());
64
+ afterEach(() => server.resetHandlers());
65
+ afterAll(() => server.close());
66
+
67
+ it('fetches events and renders them', async () => {
68
+ const dataSource = new ObjectStackAdapter({
69
+ baseUrl: BASE_URL,
70
+ });
71
+
72
+ render(
73
+ <ObjectCalendar
74
+ schema={{
75
+ type: 'calendar',
76
+ objectName: 'events',
77
+ // Calendar specific config usually goes into 'calendar' prop or implicit mapping
78
+ calendar: {
79
+ dateField: 'start',
80
+ endDateField: 'end',
81
+ titleField: 'title'
82
+ }
83
+ }}
84
+ dataSource={dataSource}
85
+ />
86
+ );
87
+
88
+ // Verify events appear
89
+ await waitFor(() => {
90
+ expect(screen.getByText('Meeting with Client')).toBeInTheDocument();
91
+ });
92
+
93
+ // Check subsequent events (might need navigation depending on view,
94
+ // but default month view usually shows current month dates)
95
+ // Note: If today is near end of month, tomorrow might be in next month view.
96
+ // However, the test event is "now", so it should be visible.
97
+
98
+ // We can just assert the first event for now.
99
+ });
100
+ });
@@ -24,26 +24,42 @@
24
24
 
25
25
  import React, { useEffect, useState, useCallback, useMemo } from 'react';
26
26
  import type { ObjectGridSchema, DataSource, ViewData, CalendarConfig } from '@object-ui/types';
27
+ import { CalendarView, type CalendarEvent } from './CalendarView';
28
+
29
+ export interface CalendarSchema {
30
+ type: 'calendar';
31
+ objectName?: string;
32
+ dateField?: string;
33
+ endField?: string;
34
+ titleField?: string;
35
+ colorField?: string;
36
+ filter?: any;
37
+ sort?: any;
38
+ /** Initial view mode */
39
+ defaultView?: 'month' | 'week' | 'day';
40
+ }
27
41
 
28
42
  export interface ObjectCalendarProps {
29
- schema: ObjectGridSchema;
43
+ schema: ObjectGridSchema | CalendarSchema;
30
44
  dataSource?: DataSource;
31
45
  className?: string;
32
46
  onEventClick?: (record: any) => void;
33
47
  onDateClick?: (date: Date) => void;
34
48
  onEdit?: (record: any) => void;
35
49
  onDelete?: (record: any) => void;
50
+ onNavigate?: (date: Date) => void;
51
+ onViewChange?: (view: 'month' | 'week' | 'day') => void;
36
52
  }
37
53
 
38
54
  /**
39
55
  * Helper to get data configuration from schema
40
56
  */
41
- function getDataConfig(schema: ObjectGridSchema): ViewData | null {
42
- if (schema.data) {
57
+ function getDataConfig(schema: ObjectGridSchema | CalendarSchema): ViewData | null {
58
+ if ('data' in schema && schema.data) {
43
59
  return schema.data;
44
60
  }
45
61
 
46
- if (schema.staticData) {
62
+ if ('staticData' in schema && schema.staticData) {
47
63
  return {
48
64
  provider: 'value',
49
65
  items: schema.staticData,
@@ -90,9 +106,9 @@ function convertSortToQueryParams(sort: string | any[] | undefined): Record<stri
90
106
  /**
91
107
  * Helper to get calendar configuration from schema
92
108
  */
93
- function getCalendarConfig(schema: ObjectGridSchema): CalendarConfig | null {
109
+ function getCalendarConfig(schema: ObjectGridSchema | CalendarSchema): CalendarConfig | null {
94
110
  // Check if schema has calendar configuration
95
- if (schema.filter && typeof schema.filter === 'object' && 'calendar' in schema.filter) {
111
+ if ('filter' in schema && schema.filter && typeof schema.filter === 'object' && 'calendar' in schema.filter) {
96
112
  return (schema.filter as any).calendar as CalendarConfig;
97
113
  }
98
114
 
@@ -101,6 +117,17 @@ function getCalendarConfig(schema: ObjectGridSchema): CalendarConfig | null {
101
117
  return (schema as any).calendar as CalendarConfig;
102
118
  }
103
119
 
120
+ // Check for flat properties (used by ObjectView)
121
+ if ((schema as any).startDateField || (schema as any).dateField) {
122
+ return {
123
+ startDateField: (schema as any).startDateField || (schema as any).dateField,
124
+ endDateField: (schema as any).endDateField || (schema as any).endField,
125
+ titleField: (schema as any).titleField || 'name',
126
+ colorField: (schema as any).colorField,
127
+ allDayField: (schema as any).allDayField
128
+ } as CalendarConfig;
129
+ }
130
+
104
131
  return null;
105
132
  }
106
133
 
@@ -110,6 +137,9 @@ export const ObjectCalendar: React.FC<ObjectCalendarProps> = ({
110
137
  className,
111
138
  onEventClick,
112
139
  onDateClick,
140
+ onNavigate,
141
+ onViewChange,
142
+ ...rest
113
143
  }) => {
114
144
  const [data, setData] = useState<any[]>([]);
115
145
  const [loading, setLoading] = useState(true);
@@ -118,22 +148,47 @@ export const ObjectCalendar: React.FC<ObjectCalendarProps> = ({
118
148
  const [currentDate, setCurrentDate] = useState(new Date());
119
149
  const [view, setView] = useState<'month' | 'week' | 'day'>('month');
120
150
 
121
- const dataConfig = getDataConfig(schema);
122
- const calendarConfig = getCalendarConfig(schema);
151
+ const dataConfig = useMemo(() => getDataConfig(schema), [
152
+ (schema as any).data,
153
+ (schema as any).staticData,
154
+ schema.objectName,
155
+ ]);
156
+ const calendarConfig = useMemo(() => getCalendarConfig(schema), [
157
+ schema.filter,
158
+ (schema as any).calendar,
159
+ (schema as any).dateField,
160
+ (schema as any).endField,
161
+ (schema as any).titleField,
162
+ (schema as any).colorField
163
+ ]);
123
164
  const hasInlineData = dataConfig?.provider === 'value';
124
165
 
125
166
  // Fetch data based on provider
126
167
  useEffect(() => {
168
+ let isMounted = true;
127
169
  const fetchData = async () => {
128
170
  try {
171
+ if (!isMounted) return;
129
172
  setLoading(true);
130
173
 
131
174
  if (hasInlineData && dataConfig?.provider === 'value') {
132
- setData(dataConfig.items as any[]);
133
- setLoading(false);
175
+ if (isMounted) {
176
+ setData(dataConfig.items as any[]);
177
+ setLoading(false);
178
+ }
134
179
  return;
135
180
  }
136
181
 
182
+ // Prioritize data passed from parent (ListView)
183
+ if ((schema as any).data || (rest as any).data) {
184
+ const passedData = (schema as any).data || (rest as any).data;
185
+ if (Array.isArray(passedData)) {
186
+ setData(passedData);
187
+ setLoading(false);
188
+ return;
189
+ }
190
+ }
191
+
137
192
  if (!dataSource) {
138
193
  throw new Error('DataSource required for object/api providers');
139
194
  }
@@ -144,20 +199,39 @@ export const ObjectCalendar: React.FC<ObjectCalendarProps> = ({
144
199
  $filter: schema.filter,
145
200
  $orderby: convertSortToQueryParams(schema.sort),
146
201
  });
147
- setData(result?.data || []);
202
+
203
+ let items: any[] = [];
204
+
205
+ if (Array.isArray(result)) {
206
+ items = result;
207
+ } else if (result && typeof result === 'object') {
208
+ if (Array.isArray((result as any).data)) {
209
+ items = (result as any).data;
210
+ } else if (Array.isArray((result as any).value)) {
211
+ items = (result as any).value;
212
+ }
213
+ }
214
+
215
+ if (isMounted) {
216
+ setData(items);
217
+ }
148
218
  } else if (dataConfig?.provider === 'api') {
149
219
  console.warn('API provider not yet implemented for ObjectCalendar');
150
- setData([]);
220
+ if (isMounted) setData([]);
151
221
  }
152
222
 
153
- setLoading(false);
223
+ if (isMounted) setLoading(false);
154
224
  } catch (err) {
155
- setError(err as Error);
156
- setLoading(false);
225
+ console.error('[ObjectCalendar] Error fetching data:', err);
226
+ if (isMounted) {
227
+ setError(err as Error);
228
+ setLoading(false);
229
+ }
157
230
  }
158
231
  };
159
232
 
160
233
  fetchData();
234
+ return () => { isMounted = false; };
161
235
  }, [dataConfig, dataSource, hasInlineData, schema.filter, schema.sort]);
162
236
 
163
237
  // Fetch object schema for field metadata
@@ -210,50 +284,13 @@ export const ObjectCalendar: React.FC<ObjectCalendarProps> = ({
210
284
  }).filter(event => !isNaN(event.start.getTime())); // Filter out invalid dates
211
285
  }, [data, calendarConfig]);
212
286
 
213
- // Get days in current month view
214
- const calendarDays = useMemo(() => {
215
- const year = currentDate.getFullYear();
216
- const month = currentDate.getMonth();
217
-
218
- const firstDay = new Date(year, month, 1);
219
- const lastDay = new Date(year, month + 1, 0);
220
- const startDay = new Date(firstDay);
221
- startDay.setDate(startDay.getDate() - startDay.getDay()); // Start from Sunday
222
-
223
- const days: Date[] = [];
224
- const current = new Date(startDay);
225
-
226
- // Get 6 weeks worth of days
227
- for (let i = 0; i < 42; i++) {
228
- days.push(new Date(current));
229
- current.setDate(current.getDate() + 1);
230
- }
231
-
232
- return days;
233
- }, [currentDate]);
234
-
235
- // Get events for a specific day
236
- const getEventsForDay = useCallback((day: Date) => {
237
- return events.filter(event => {
238
- const eventStart = new Date(event.start);
239
- eventStart.setHours(0, 0, 0, 0);
240
- const eventEnd = event.end ? new Date(event.end) : eventStart;
241
- eventEnd.setHours(23, 59, 59, 999);
242
-
243
- const checkDay = new Date(day);
244
- checkDay.setHours(0, 0, 0, 0);
245
-
246
- return checkDay >= eventStart && checkDay <= eventEnd;
247
- });
248
- }, [events]);
249
-
250
- const navigateMonth = useCallback((direction: 'prev' | 'next') => {
251
- setCurrentDate(prev => {
252
- const newDate = new Date(prev);
253
- newDate.setMonth(newDate.getMonth() + (direction === 'next' ? 1 : -1));
254
- return newDate;
255
- });
256
- }, []);
287
+ // Get days in current month view - REMOVED (Handled by CalendarView)
288
+
289
+ const handleCreate = useCallback(() => {
290
+ // Standard "Create" action trigger
291
+ const today = new Date();
292
+ onDateClick?.(today);
293
+ }, [onDateClick]);
257
294
 
258
295
  if (loading) {
259
296
  return (
@@ -287,98 +324,25 @@ export const ObjectCalendar: React.FC<ObjectCalendarProps> = ({
287
324
  );
288
325
  }
289
326
 
290
- const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
291
- 'July', 'August', 'September', 'October', 'November', 'December'];
292
- const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
293
-
294
327
  return (
295
328
  <div className={className}>
296
- <div className="border rounded-lg bg-background">
297
- {/* Calendar Header */}
298
- <div className="flex items-center justify-between p-4 border-b">
299
- <h2 className="text-xl font-semibold">
300
- {monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
301
- </h2>
302
- <div className="flex gap-2">
303
- <button
304
- onClick={() => navigateMonth('prev')}
305
- className="px-3 py-1 border rounded hover:bg-muted"
306
- >
307
- Previous
308
- </button>
309
- <button
310
- onClick={() => setCurrentDate(new Date())}
311
- className="px-3 py-1 border rounded hover:bg-muted"
312
- >
313
- Today
314
- </button>
315
- <button
316
- onClick={() => navigateMonth('next')}
317
- className="px-3 py-1 border rounded hover:bg-muted"
318
- >
319
- Next
320
- </button>
321
- </div>
322
- </div>
323
-
324
- {/* Calendar Grid */}
325
- <div className="p-4">
326
- {/* Day Headers */}
327
- <div className="grid grid-cols-7 gap-px mb-px">
328
- {dayNames.map(day => (
329
- <div
330
- key={day}
331
- className="text-center text-sm font-medium text-muted-foreground py-2"
332
- >
333
- {day}
334
- </div>
335
- ))}
336
- </div>
337
-
338
- {/* Calendar Days */}
339
- <div className="grid grid-cols-7 gap-px bg-border">
340
- {calendarDays.map((day, index) => {
341
- const dayEvents = getEventsForDay(day);
342
- const isCurrentMonth = day.getMonth() === currentDate.getMonth();
343
- const isToday =
344
- day.getDate() === new Date().getDate() &&
345
- day.getMonth() === new Date().getMonth() &&
346
- day.getFullYear() === new Date().getFullYear();
347
-
348
- return (
349
- <div
350
- key={index}
351
- className={`min-h-24 bg-background p-2 ${
352
- !isCurrentMonth ? 'text-muted-foreground bg-muted/30' : ''
353
- } ${isToday ? 'ring-2 ring-primary' : ''}`}
354
- onClick={() => onDateClick?.(day)}
355
- >
356
- <div className="text-sm font-medium mb-1">{day.getDate()}</div>
357
- <div className="space-y-1">
358
- {dayEvents.slice(0, 3).map(event => (
359
- <div
360
- key={event.id}
361
- className="text-xs px-1 py-0.5 rounded bg-primary/10 hover:bg-primary/20 cursor-pointer truncate"
362
- onClick={(e) => {
363
- e.stopPropagation();
364
- onEventClick?.(event.data);
365
- }}
366
- style={event.color ? { borderLeft: `3px solid ${event.color}` } : undefined}
367
- >
368
- {event.title}
369
- </div>
370
- ))}
371
- {dayEvents.length > 3 && (
372
- <div className="text-xs text-muted-foreground">
373
- +{dayEvents.length - 3} more
374
- </div>
375
- )}
376
- </div>
377
- </div>
378
- );
379
- })}
380
- </div>
381
- </div>
329
+ <div className="border rounded-lg bg-background h-[calc(100vh-200px)] min-h-[600px]">
330
+ <CalendarView
331
+ events={events}
332
+ currentDate={currentDate}
333
+ view={(schema as any).defaultView || 'month'}
334
+ onEventClick={(event) => onEventClick?.(event.data)}
335
+ onDateClick={onDateClick}
336
+ onNavigate={(date) => {
337
+ setCurrentDate(date);
338
+ onNavigate?.(date);
339
+ }}
340
+ onViewChange={(v) => {
341
+ setView(v);
342
+ onViewChange?.(v);
343
+ }}
344
+ onAddClick={handleCreate}
345
+ />
382
346
  </div>
383
347
  </div>
384
348
  );
@@ -7,8 +7,8 @@
7
7
  */
8
8
 
9
9
  import { ComponentRegistry } from '@object-ui/core';
10
- import type { CalendarViewSchema, CalendarEvent } from '@object-ui/types';
11
- import { CalendarView } from './CalendarView';
10
+ import type { CalendarViewSchema } from '@object-ui/types';
11
+ import { CalendarView, type CalendarEvent } from './CalendarView';
12
12
  import React from 'react';
13
13
 
14
14
  // Calendar View Renderer - Airtable-style calendar for displaying records as events
@@ -30,96 +30,52 @@ ComponentRegistry.register('calendar-view',
30
30
  /** Field name indicating if event is all-day */
31
31
  const allDayField = schema.allDayField || 'allDay';
32
32
 
33
- const title = record[titleField] || 'Untitled';
34
- const start = record[startField] ? new Date(record[startField]) : new Date();
35
- const end = record[endField] ? new Date(record[endField]) : undefined;
36
- const allDay = record[allDayField] !== undefined ? record[allDayField] : false;
37
-
38
- // Handle color mapping
39
- let color = record[colorField];
40
- if (color && schema.colorMapping && schema.colorMapping[color]) {
41
- color = schema.colorMapping[color];
42
- }
43
-
44
33
  return {
45
- id: String(record.id || record._id || index),
46
- title,
47
- start,
48
- end,
49
- allDay,
50
- color,
34
+ id: record._id || record.id || index,
35
+ title: record[titleField] || 'Untitled Event',
36
+ start: new Date(record[startField]),
37
+ end: record[endField] ? new Date(record[endField]) : undefined,
38
+ allDay: record[allDayField],
39
+ color: record[colorField],
51
40
  data: record,
52
41
  };
53
42
  });
54
- }, [schema.data, schema.titleField, schema.startDateField, schema.endDateField, schema.colorField, schema.allDayField, schema.colorMapping]);
43
+ }, [schema.data, schema.titleField, schema.startDateField, schema.endDateField, schema.colorField, schema.allDayField]);
55
44
 
56
- const handleEventClick = React.useCallback((event: any) => {
57
- if (onAction) {
58
- onAction({
59
- type: 'event_click',
60
- payload: { event: event.data, eventId: event.id }
61
- });
62
- }
45
+ const handleEventClick = (event: CalendarEvent) => {
63
46
  if (schema.onEventClick) {
64
- schema.onEventClick(event.data);
65
- }
66
- }, [onAction, schema]);
67
-
68
- const handleDateClick = React.useCallback((date: Date) => {
69
- if (onAction) {
70
- onAction({
71
- type: 'date_click',
72
- payload: { date }
73
- });
74
- }
75
- if (schema.onDateClick) {
76
- schema.onDateClick(date);
77
- }
78
- }, [onAction, schema]);
79
-
80
- const handleViewChange = React.useCallback((view: "month" | "week" | "day") => {
81
- if (onAction) {
82
- onAction({
83
- type: 'view_change',
84
- payload: { view }
85
- });
86
- }
87
- if (schema.onViewChange) {
88
- schema.onViewChange(view);
89
- }
90
- }, [onAction, schema]);
91
-
92
- const handleNavigate = React.useCallback((date: Date) => {
93
- if (onAction) {
94
- onAction({
95
- type: 'navigate',
96
- payload: { date }
47
+ // Dispatch configured action
48
+ // This would use the action runner in a real implementation
49
+ // For now we just call onAction if provided
50
+ onAction?.({
51
+ type: 'event-click',
52
+ payload: event
97
53
  });
98
54
  }
99
- if (schema.onNavigate) {
100
- schema.onNavigate(date);
101
- }
102
- }, [onAction, schema]);
103
-
104
- const validView = (schema.view && ['month', 'week', 'day'].includes(schema.view))
105
- ? (schema.view as "month" | "week" | "day")
106
- : 'month';
55
+ };
56
+
57
+ const handleAddClick = () => {
58
+ // Standard "Create" action trigger
59
+ onAction?.({
60
+ type: 'create',
61
+ payload: {}
62
+ });
63
+ };
107
64
 
108
65
  return (
109
- <CalendarView
66
+ <CalendarView
67
+ className={className}
110
68
  events={events}
111
- view={validView}
112
- currentDate={schema.currentDate ? new Date(schema.currentDate) : undefined}
113
69
  onEventClick={handleEventClick}
114
- onDateClick={schema.allowCreate || schema.onDateClick ? handleDateClick : undefined}
115
- onViewChange={handleViewChange}
116
- onNavigate={handleNavigate}
117
- className={className}
70
+ onAddClick={handleAddClick}
71
+ // Pass validation or other props
118
72
  {...props}
119
73
  />
120
74
  );
121
- },
75
+ }
76
+ ,
122
77
  {
78
+ namespace: 'plugin-calendar',
123
79
  label: 'Calendar View',
124
80
  inputs: [
125
81
  {
package/src/index.tsx CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  import React from 'react';
10
10
  import { ComponentRegistry } from '@object-ui/core';
11
+ import { useSchemaContext } from '@object-ui/react';
11
12
  import { ObjectCalendar } from './ObjectCalendar';
12
13
  import type { ObjectCalendarProps } from './ObjectCalendar';
13
14
 
@@ -23,13 +24,25 @@ export type { CalendarViewProps, CalendarEvent } from './CalendarView';
23
24
  import './calendar-view-renderer';
24
25
 
25
26
  // Register object-calendar component
26
- const ObjectCalendarRenderer: React.FC<{ schema: any }> = ({ schema }) => {
27
- return <ObjectCalendar schema={schema} dataSource={null as any} />;
27
+ export const ObjectCalendarRenderer: React.FC<{ schema: any }> = ({ schema }) => {
28
+ const { dataSource } = useSchemaContext();
29
+ return <ObjectCalendar schema={schema} dataSource={dataSource} />;
28
30
  };
29
31
 
30
32
  ComponentRegistry.register('object-calendar', ObjectCalendarRenderer, {
33
+ namespace: 'plugin-calendar',
31
34
  label: 'Object Calendar',
32
- category: 'plugin',
35
+ category: 'view',
36
+ inputs: [
37
+ { name: 'objectName', type: 'string', label: 'Object Name', required: true },
38
+ { name: 'calendar', type: 'object', label: 'Calendar Config', description: 'startDateField, endDateField, titleField, colorField' },
39
+ ],
40
+ });
41
+
42
+ ComponentRegistry.register('calendar', ObjectCalendarRenderer, {
43
+ namespace: 'view',
44
+ label: 'Calendar View',
45
+ category: 'view',
33
46
  inputs: [
34
47
  { name: 'objectName', type: 'string', label: 'Object Name', required: true },
35
48
  { name: 'calendar', type: 'object', label: 'Calendar Config', description: 'startDateField, endDateField, titleField, colorField' },
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import React from 'react';
4
+ import { ObjectCalendarRenderer } from './index';
5
+
6
+ // Mock dependencies
7
+ vi.mock('@object-ui/react', () => ({
8
+ useSchemaContext: vi.fn(() => ({ dataSource: { type: 'mock-datasource' } })),
9
+ }));
10
+
11
+ // Mock the implementation
12
+ vi.mock('./ObjectCalendar', () => ({
13
+ ObjectCalendar: ({ dataSource }: any) => (
14
+ <div data-testid="calendar-mock">
15
+ {dataSource ? `DataSource: ${dataSource.type}` : 'No DataSource'}
16
+ </div>
17
+ )
18
+ }));
19
+
20
+ describe('Plugin Calendar Registration', () => {
21
+ it('renderer passes dataSource from context', () => {
22
+ render(<ObjectCalendarRenderer schema={{ type: 'object-calendar' }} />);
23
+ expect(screen.getByTestId('calendar-mock')).toHaveTextContent('DataSource: mock-datasource');
24
+ });
25
+ });