Skip to content

Commit 48ccef1

Browse files
authored
Merge pull request #526 from objectstack-ai/copilot/fix-calendar-view-issue
2 parents a1bd0ac + 345b229 commit 48ccef1

File tree

2 files changed

+166
-34
lines changed

2 files changed

+166
-34
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { render, screen, waitFor } from '@testing-library/react';
3+
import { ObjectView } from '../components/ObjectView';
4+
import { SchemaRendererProvider } from '@object-ui/react';
5+
import '@object-ui/plugin-grid';
6+
import '@object-ui/plugin-kanban';
7+
import '@object-ui/plugin-calendar';
8+
import '@object-ui/plugin-list';
9+
10+
vi.mock('@object-ui/components', async () => {
11+
const actual = await vi.importActual('@object-ui/components');
12+
return { ...actual };
13+
});
14+
15+
vi.mock('@object-ui/react', async (importOriginal) => {
16+
const actual = await importOriginal<any>();
17+
return {
18+
...actual,
19+
useDataScope: () => undefined,
20+
};
21+
});
22+
23+
const mockSetSearchParams = vi.fn();
24+
let mockSearchParams = new URLSearchParams();
25+
26+
vi.mock('react-router-dom', () => ({
27+
useParams: () => ({ objectName: 'event', viewId: 'calendar' }),
28+
useSearchParams: () => [mockSearchParams, mockSetSearchParams],
29+
useNavigate: () => vi.fn(),
30+
}));
31+
32+
// Use dates in current month so events show on the visible calendar (month view shows all days 1-31)
33+
const now = new Date();
34+
const year = now.getFullYear();
35+
const month = String(now.getMonth() + 1).padStart(2, '0');
36+
37+
const mockDataSource = {
38+
find: vi.fn().mockResolvedValue([
39+
{ _id: 'e1', subject: 'Weekly Standup', start: `${year}-${month}-05T09:00:00`, end: `${year}-${month}-05T10:00:00`, type: 'Meeting' },
40+
{ _id: 'e2', subject: 'Client Call', start: `${year}-${month}-06T14:00:00`, end: `${year}-${month}-06T15:00:00`, type: 'Call' }
41+
]),
42+
findOne: vi.fn().mockResolvedValue({}),
43+
create: vi.fn().mockResolvedValue({}),
44+
update: vi.fn().mockResolvedValue({}),
45+
delete: vi.fn().mockResolvedValue(true),
46+
getObjectSchema: vi.fn().mockResolvedValue({
47+
name: 'event',
48+
label: 'Event',
49+
fields: {
50+
subject: { label: 'Subject', type: 'text' },
51+
start: { label: 'Start', type: 'datetime' },
52+
end: { label: 'End', type: 'datetime' },
53+
type: { label: 'Type', type: 'select' },
54+
}
55+
})
56+
};
57+
58+
// CRM-style nested calendar config (same structure as objectstack.config.ts)
59+
const mockObjects = [
60+
{
61+
name: 'event',
62+
label: 'Event',
63+
fields: {
64+
subject: { label: 'Subject', type: 'text' },
65+
start: { label: 'Start', type: 'datetime' },
66+
end: { label: 'End', type: 'datetime' },
67+
type: { label: 'Type', type: 'select' },
68+
},
69+
listViews: {
70+
all_events: {
71+
name: 'all_events',
72+
label: 'All Events',
73+
type: 'grid',
74+
columns: ['subject', 'start', 'end', 'type'],
75+
},
76+
calendar: {
77+
name: 'calendar',
78+
label: 'Calendar',
79+
type: 'calendar',
80+
columns: ['subject', 'start', 'end', 'type'],
81+
calendar: {
82+
startDateField: 'start',
83+
endDateField: 'end',
84+
titleField: 'subject',
85+
},
86+
},
87+
},
88+
},
89+
];
90+
91+
describe('CRM Calendar View with Nested Config', () => {
92+
beforeEach(() => {
93+
vi.clearAllMocks();
94+
mockSearchParams = new URLSearchParams();
95+
});
96+
97+
it('renders calendar view with events using nested calendar config', async () => {
98+
render(
99+
<SchemaRendererProvider dataSource={mockDataSource}>
100+
<ObjectView dataSource={mockDataSource} objects={mockObjects} onEdit={vi.fn()} />
101+
</SchemaRendererProvider>
102+
);
103+
104+
// Calendar region should render
105+
await waitFor(() => {
106+
const calendarRegion = document.querySelector('[aria-label="Calendar"]');
107+
expect(calendarRegion).toBeInTheDocument();
108+
}, { timeout: 5000 });
109+
110+
// Event titles should display correctly using titleField: 'subject'
111+
await waitFor(() => {
112+
expect(screen.getByText('Weekly Standup')).toBeInTheDocument();
113+
}, { timeout: 5000 });
114+
});
115+
116+
it('does not show "Untitled" events when nested calendar config provides titleField', async () => {
117+
render(
118+
<SchemaRendererProvider dataSource={mockDataSource}>
119+
<ObjectView dataSource={mockDataSource} objects={mockObjects} onEdit={vi.fn()} />
120+
</SchemaRendererProvider>
121+
);
122+
123+
// Wait for calendar region to render and data to load
124+
await waitFor(() => {
125+
const calendarRegion = document.querySelector('[aria-label="Calendar"]');
126+
expect(calendarRegion).toBeInTheDocument();
127+
}, { timeout: 5000 });
128+
129+
// Should NOT show "Untitled" - that would mean titleField defaulted to 'name'
130+
expect(screen.queryByText('Untitled')).not.toBeInTheDocument();
131+
});
132+
});

apps/console/src/components/ObjectView.tsx

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -165,52 +165,52 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) {
165165
...listSchema,
166166
options: {
167167
kanban: {
168-
groupBy: viewDef.groupBy || viewDef.groupField || 'status',
169-
groupField: viewDef.groupBy || viewDef.groupField || 'status',
170-
titleField: viewDef.titleField || objectDef.titleField || 'name',
171-
cardFields: viewDef.columns || viewDef.cardFields,
168+
groupBy: viewDef.kanban?.groupByField || viewDef.kanban?.groupField || viewDef.groupBy || viewDef.groupField || 'status',
169+
groupField: viewDef.kanban?.groupByField || viewDef.kanban?.groupField || viewDef.groupBy || viewDef.groupField || 'status',
170+
titleField: viewDef.kanban?.titleField || viewDef.titleField || objectDef.titleField || 'name',
171+
cardFields: viewDef.kanban?.columns || viewDef.columns || viewDef.cardFields,
172172
},
173173
calendar: {
174-
startDateField: viewDef.startDateField || viewDef.dateField || 'due_date',
175-
endDateField: viewDef.endDateField || viewDef.endField,
176-
titleField: viewDef.titleField || viewDef.subjectField || 'name',
177-
colorField: viewDef.colorField,
178-
allDayField: viewDef.allDayField,
179-
defaultView: viewDef.defaultView,
174+
startDateField: viewDef.calendar?.startDateField || viewDef.startDateField || viewDef.dateField || 'due_date',
175+
endDateField: viewDef.calendar?.endDateField || viewDef.endDateField || viewDef.endField,
176+
titleField: viewDef.calendar?.titleField || viewDef.titleField || viewDef.subjectField || 'name',
177+
colorField: viewDef.calendar?.colorField || viewDef.colorField,
178+
allDayField: viewDef.calendar?.allDayField || viewDef.allDayField,
179+
defaultView: viewDef.calendar?.defaultView || viewDef.defaultView,
180180
},
181181
timeline: {
182-
dateField: viewDef.dateField || viewDef.startDateField || 'due_date',
183-
titleField: viewDef.titleField || objectDef.titleField || 'name',
184-
descriptionField: viewDef.descriptionField,
182+
dateField: viewDef.timeline?.dateField || viewDef.dateField || viewDef.startDateField || 'due_date',
183+
titleField: viewDef.timeline?.titleField || viewDef.titleField || objectDef.titleField || 'name',
184+
descriptionField: viewDef.timeline?.descriptionField || viewDef.descriptionField,
185185
},
186186
map: {
187-
locationField: viewDef.locationField,
188-
titleField: viewDef.titleField || objectDef.titleField || 'name',
189-
latitudeField: viewDef.latitudeField,
190-
longitudeField: viewDef.longitudeField,
191-
zoom: viewDef.zoom,
192-
center: viewDef.center,
187+
locationField: viewDef.map?.locationField || viewDef.locationField,
188+
titleField: viewDef.map?.titleField || viewDef.titleField || objectDef.titleField || 'name',
189+
latitudeField: viewDef.map?.latitudeField || viewDef.latitudeField,
190+
longitudeField: viewDef.map?.longitudeField || viewDef.longitudeField,
191+
zoom: viewDef.map?.zoom || viewDef.zoom,
192+
center: viewDef.map?.center || viewDef.center,
193193
},
194194
gallery: {
195-
imageField: viewDef.imageField || 'image',
196-
titleField: viewDef.titleField || objectDef.titleField || 'name',
197-
subtitleField: viewDef.subtitleField,
195+
imageField: viewDef.gallery?.imageField || viewDef.imageField || 'image',
196+
titleField: viewDef.gallery?.titleField || viewDef.titleField || objectDef.titleField || 'name',
197+
subtitleField: viewDef.gallery?.subtitleField || viewDef.subtitleField,
198198
},
199199
gantt: {
200-
startDateField: viewDef.startDateField || 'start_date',
201-
endDateField: viewDef.endDateField || 'end_date',
202-
titleField: viewDef.titleField || 'name',
203-
progressField: viewDef.progressField,
204-
dependenciesField: viewDef.dependenciesField,
205-
colorField: viewDef.colorField,
200+
startDateField: viewDef.gantt?.startDateField || viewDef.startDateField || 'start_date',
201+
endDateField: viewDef.gantt?.endDateField || viewDef.endDateField || 'end_date',
202+
titleField: viewDef.gantt?.titleField || viewDef.titleField || 'name',
203+
progressField: viewDef.gantt?.progressField || viewDef.progressField,
204+
dependenciesField: viewDef.gantt?.dependenciesField || viewDef.dependenciesField,
205+
colorField: viewDef.gantt?.colorField || viewDef.colorField,
206206
},
207207
chart: {
208-
chartType: viewDef.chartType,
209-
xAxisField: viewDef.xAxisField,
210-
yAxisFields: viewDef.yAxisFields,
211-
aggregation: viewDef.aggregation,
212-
series: viewDef.series,
213-
config: viewDef.config,
208+
chartType: viewDef.chart?.chartType || viewDef.chartType,
209+
xAxisField: viewDef.chart?.xAxisField || viewDef.xAxisField,
210+
yAxisFields: viewDef.chart?.yAxisFields || viewDef.yAxisFields,
211+
aggregation: viewDef.chart?.aggregation || viewDef.aggregation,
212+
series: viewDef.chart?.series || viewDef.series,
213+
config: viewDef.chart?.config || viewDef.config,
214214
},
215215
},
216216
};

0 commit comments

Comments
 (0)