@object-ui/plugin-calendar 3.3.0 → 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.
- package/CHANGELOG.md +13 -0
- package/README.md +21 -1
- package/dist/index.js +1 -1
- package/dist/index.umd.cjs +1 -1
- package/package.json +36 -13
- package/.turbo/turbo-build.log +0 -22
- package/src/CalendarView.test.tsx +0 -118
- package/src/CalendarView.tsx +0 -821
- package/src/ObjectCalendar.msw.test.tsx +0 -104
- package/src/ObjectCalendar.stories.tsx +0 -82
- package/src/ObjectCalendar.tsx +0 -433
- package/src/__tests__/accessibility.test.tsx +0 -290
- package/src/__tests__/calendar-bugfixes.test.tsx +0 -230
- package/src/__tests__/calendar-optimizations.test.tsx +0 -178
- package/src/__tests__/performance-benchmark.test.tsx +0 -227
- package/src/__tests__/view-states.test.tsx +0 -377
- package/src/calendar-view-renderer.tsx +0 -181
- package/src/index.tsx +0 -50
- package/src/registration.test.tsx +0 -41
- package/test/setup.ts +0 -32
- package/tsconfig.json +0 -18
- package/vite.config.ts +0 -57
- package/vitest.config.ts +0 -13
- package/vitest.setup.ts +0 -1
|
@@ -1,227 +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
|
-
* Performance benchmark tests for CalendarView.
|
|
9
|
-
* Part of P2.4 Performance at Scale roadmap.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
13
|
-
import { render, screen } from '@testing-library/react';
|
|
14
|
-
import '@testing-library/jest-dom';
|
|
15
|
-
import React from 'react';
|
|
16
|
-
import { CalendarView, type CalendarEvent, type CalendarViewProps } from '../CalendarView';
|
|
17
|
-
|
|
18
|
-
// Mock @object-ui/components
|
|
19
|
-
vi.mock('@object-ui/components', () => ({
|
|
20
|
-
cn: (...classes: (string | undefined | boolean)[]) => classes.filter(Boolean).join(' '),
|
|
21
|
-
Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
|
|
22
|
-
Select: ({ children, value, onValueChange }: any) => <div data-testid="select">{children}</div>,
|
|
23
|
-
SelectContent: ({ children }: any) => <div>{children}</div>,
|
|
24
|
-
SelectItem: ({ children, value }: any) => <div data-value={value}>{children}</div>,
|
|
25
|
-
SelectTrigger: ({ children }: any) => <div>{children}</div>,
|
|
26
|
-
SelectValue: () => <span>Month</span>,
|
|
27
|
-
Calendar: (props: any) => <div data-testid="calendar-picker">Calendar Picker</div>,
|
|
28
|
-
Popover: ({ children }: any) => <div>{children}</div>,
|
|
29
|
-
PopoverContent: ({ children }: any) => <div>{children}</div>,
|
|
30
|
-
PopoverTrigger: ({ children }: any) => <div>{children}</div>,
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
// Mock lucide-react icons
|
|
34
|
-
vi.mock('lucide-react', () => ({
|
|
35
|
-
ChevronLeftIcon: (props: any) => <svg data-testid="chevron-left" {...props} />,
|
|
36
|
-
ChevronRightIcon: (props: any) => <svg data-testid="chevron-right" {...props} />,
|
|
37
|
-
CalendarIcon: (props: any) => <svg data-testid="calendar-icon" {...props} />,
|
|
38
|
-
PlusIcon: (props: any) => <svg data-testid="plus-icon" {...props} />,
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
// Mock ResizeObserver
|
|
42
|
-
class ResizeObserver {
|
|
43
|
-
observe() {}
|
|
44
|
-
unobserve() {}
|
|
45
|
-
disconnect() {}
|
|
46
|
-
}
|
|
47
|
-
global.ResizeObserver = ResizeObserver;
|
|
48
|
-
|
|
49
|
-
// --- Data generators ---
|
|
50
|
-
|
|
51
|
-
const baseDate = new Date(2024, 0, 15); // Jan 15, 2024
|
|
52
|
-
|
|
53
|
-
function generateEvents(count: number): CalendarEvent[] {
|
|
54
|
-
const events: CalendarEvent[] = [];
|
|
55
|
-
for (let i = 0; i < count; i++) {
|
|
56
|
-
const day = (i % 28) + 1;
|
|
57
|
-
const hour = (i % 12) + 8;
|
|
58
|
-
events.push({
|
|
59
|
-
id: `event-${i}`,
|
|
60
|
-
title: `Event ${i}`,
|
|
61
|
-
start: new Date(2024, 0, day, hour, 0),
|
|
62
|
-
end: new Date(2024, 0, day, hour + 1, 0),
|
|
63
|
-
allDay: i % 10 === 0,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
return events;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function generateMultiDayEvents(count: number): CalendarEvent[] {
|
|
70
|
-
const events: CalendarEvent[] = [];
|
|
71
|
-
for (let i = 0; i < count; i++) {
|
|
72
|
-
const startDay = (i % 25) + 1;
|
|
73
|
-
const spanDays = (i % 4) + 2; // 2-5 day span
|
|
74
|
-
events.push({
|
|
75
|
-
id: `multi-event-${i}`,
|
|
76
|
-
title: `Multi-day Event ${i}`,
|
|
77
|
-
start: new Date(2024, 0, startDay, 9, 0),
|
|
78
|
-
end: new Date(2024, 0, startDay + spanDays, 17, 0),
|
|
79
|
-
allDay: true,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return events;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function renderCalendar(overrides: Partial<CalendarViewProps> = {}) {
|
|
86
|
-
const props: CalendarViewProps = {
|
|
87
|
-
currentDate: baseDate,
|
|
88
|
-
locale: 'en-US',
|
|
89
|
-
...overrides,
|
|
90
|
-
};
|
|
91
|
-
return render(<CalendarView {...props} />);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// =========================================================================
|
|
95
|
-
// Performance Benchmarks
|
|
96
|
-
// =========================================================================
|
|
97
|
-
|
|
98
|
-
describe('CalendarView: performance benchmarks', () => {
|
|
99
|
-
it('renders with 100 events under 500ms', () => {
|
|
100
|
-
const events = generateEvents(100);
|
|
101
|
-
|
|
102
|
-
const start = performance.now();
|
|
103
|
-
const { container } = renderCalendar({ events });
|
|
104
|
-
const elapsed = performance.now() - start;
|
|
105
|
-
|
|
106
|
-
expect(container).toBeTruthy();
|
|
107
|
-
expect(elapsed).toBeLessThan(500);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('renders with 500 events under 1,000ms', () => {
|
|
111
|
-
const events = generateEvents(500);
|
|
112
|
-
|
|
113
|
-
const start = performance.now();
|
|
114
|
-
const { container } = renderCalendar({ events });
|
|
115
|
-
const elapsed = performance.now() - start;
|
|
116
|
-
|
|
117
|
-
expect(container).toBeTruthy();
|
|
118
|
-
expect(elapsed).toBeLessThan(1_000);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('renders with 1,000 events under 2,000ms', () => {
|
|
122
|
-
const events = generateEvents(1_000);
|
|
123
|
-
|
|
124
|
-
const start = performance.now();
|
|
125
|
-
const { container } = renderCalendar({ events });
|
|
126
|
-
const elapsed = performance.now() - start;
|
|
127
|
-
|
|
128
|
-
expect(container).toBeTruthy();
|
|
129
|
-
expect(elapsed).toBeLessThan(2_000);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('renders with no events instantly', () => {
|
|
133
|
-
const start = performance.now();
|
|
134
|
-
const { container } = renderCalendar({ events: [] });
|
|
135
|
-
const elapsed = performance.now() - start;
|
|
136
|
-
|
|
137
|
-
expect(container).toBeTruthy();
|
|
138
|
-
expect(elapsed).toBeLessThan(200);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('data generation for 1,000 events is fast (< 100ms)', () => {
|
|
142
|
-
const start = performance.now();
|
|
143
|
-
const events = generateEvents(1_000);
|
|
144
|
-
const elapsed = performance.now() - start;
|
|
145
|
-
|
|
146
|
-
expect(events).toHaveLength(1_000);
|
|
147
|
-
expect(elapsed).toBeLessThan(100);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// =========================================================================
|
|
152
|
-
// Multi-day event scaling
|
|
153
|
-
// =========================================================================
|
|
154
|
-
|
|
155
|
-
describe('CalendarView: multi-day event performance', () => {
|
|
156
|
-
it('renders 100 multi-day events under 500ms', () => {
|
|
157
|
-
const events = generateMultiDayEvents(100);
|
|
158
|
-
|
|
159
|
-
const start = performance.now();
|
|
160
|
-
const { container } = renderCalendar({ events });
|
|
161
|
-
const elapsed = performance.now() - start;
|
|
162
|
-
|
|
163
|
-
expect(container).toBeTruthy();
|
|
164
|
-
expect(elapsed).toBeLessThan(500);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('renders 500 multi-day events under 1,500ms', () => {
|
|
168
|
-
const events = generateMultiDayEvents(500);
|
|
169
|
-
|
|
170
|
-
const start = performance.now();
|
|
171
|
-
const { container } = renderCalendar({ events });
|
|
172
|
-
const elapsed = performance.now() - start;
|
|
173
|
-
|
|
174
|
-
expect(container).toBeTruthy();
|
|
175
|
-
expect(elapsed).toBeLessThan(1_500);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('renders mix of single-day and multi-day events at scale', () => {
|
|
179
|
-
const singleDay = generateEvents(500);
|
|
180
|
-
const multiDay = generateMultiDayEvents(500);
|
|
181
|
-
const events = [...singleDay, ...multiDay];
|
|
182
|
-
|
|
183
|
-
const start = performance.now();
|
|
184
|
-
const { container } = renderCalendar({ events });
|
|
185
|
-
const elapsed = performance.now() - start;
|
|
186
|
-
|
|
187
|
-
expect(container).toBeTruthy();
|
|
188
|
-
expect(elapsed).toBeLessThan(2_000);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// =========================================================================
|
|
193
|
-
// Scaling across views
|
|
194
|
-
// =========================================================================
|
|
195
|
-
|
|
196
|
-
describe('CalendarView: scaling across views', () => {
|
|
197
|
-
it('renders month view with 500 events under 1,000ms', () => {
|
|
198
|
-
const events = generateEvents(500);
|
|
199
|
-
|
|
200
|
-
const start = performance.now();
|
|
201
|
-
renderCalendar({ events, view: 'month' });
|
|
202
|
-
const elapsed = performance.now() - start;
|
|
203
|
-
|
|
204
|
-
expect(elapsed).toBeLessThan(1_000);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('renders day view with 500 events under 500ms', () => {
|
|
208
|
-
const events = generateEvents(500);
|
|
209
|
-
|
|
210
|
-
const start = performance.now();
|
|
211
|
-
renderCalendar({ events, view: 'day' });
|
|
212
|
-
const elapsed = performance.now() - start;
|
|
213
|
-
|
|
214
|
-
expect(elapsed).toBeLessThan(500);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('handles events with all-day flag at scale', () => {
|
|
218
|
-
const events = generateEvents(200).map((e) => ({ ...e, allDay: true }));
|
|
219
|
-
|
|
220
|
-
const start = performance.now();
|
|
221
|
-
const { container } = renderCalendar({ events });
|
|
222
|
-
const elapsed = performance.now() - start;
|
|
223
|
-
|
|
224
|
-
expect(container).toBeTruthy();
|
|
225
|
-
expect(elapsed).toBeLessThan(1_000);
|
|
226
|
-
});
|
|
227
|
-
});
|
|
@@ -1,377 +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
|
-
/**
|
|
10
|
-
* P3.3 Plugin View Robustness - Calendar View States
|
|
11
|
-
*
|
|
12
|
-
* Tests empty, populated, and edge-case states for CalendarView component.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
16
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
17
|
-
import '@testing-library/jest-dom';
|
|
18
|
-
import React from 'react';
|
|
19
|
-
import { CalendarView, type CalendarEvent } from '../CalendarView';
|
|
20
|
-
|
|
21
|
-
// Mock ResizeObserver
|
|
22
|
-
class ResizeObserver {
|
|
23
|
-
observe() {}
|
|
24
|
-
unobserve() {}
|
|
25
|
-
disconnect() {}
|
|
26
|
-
}
|
|
27
|
-
global.ResizeObserver = ResizeObserver;
|
|
28
|
-
|
|
29
|
-
// Mock PointerEvents for Radix
|
|
30
|
-
if (!global.PointerEvent) {
|
|
31
|
-
class PointerEvent extends Event {
|
|
32
|
-
button: number;
|
|
33
|
-
ctrlKey: boolean;
|
|
34
|
-
metaKey: boolean;
|
|
35
|
-
shiftKey: boolean;
|
|
36
|
-
constructor(type: string, props: any = {}) {
|
|
37
|
-
super(type, props);
|
|
38
|
-
this.button = props.button || 0;
|
|
39
|
-
this.ctrlKey = props.ctrlKey || false;
|
|
40
|
-
this.metaKey = props.metaKey || false;
|
|
41
|
-
this.shiftKey = props.shiftKey || false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// @ts-expect-error Mocking global PointerEvent
|
|
45
|
-
global.PointerEvent = PointerEvent as any;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Mock HTMLElement.offsetParent for Radix Popper
|
|
49
|
-
Object.defineProperty(HTMLElement.prototype, 'offsetParent', {
|
|
50
|
-
get() {
|
|
51
|
-
return this.parentElement;
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const defaultDate = new Date(2024, 0, 15); // Jan 15, 2024
|
|
56
|
-
|
|
57
|
-
describe('P3.3 Calendar View States', () => {
|
|
58
|
-
// ---------------------------------------------------------------
|
|
59
|
-
// Empty state (no events)
|
|
60
|
-
// ---------------------------------------------------------------
|
|
61
|
-
describe('empty state', () => {
|
|
62
|
-
it('renders header with no events', () => {
|
|
63
|
-
render(<CalendarView currentDate={defaultDate} events={[]} locale="en-US" />);
|
|
64
|
-
expect(screen.getByText('January 2024')).toBeInTheDocument();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('renders navigation controls with no events', () => {
|
|
68
|
-
render(<CalendarView currentDate={defaultDate} events={[]} locale="en-US" />);
|
|
69
|
-
expect(screen.getByText('Today')).toBeInTheDocument();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('renders day headers in month view with no events', () => {
|
|
73
|
-
render(<CalendarView currentDate={defaultDate} events={[]} locale="en-US" view="month" />);
|
|
74
|
-
expect(screen.getByText('Sun')).toBeInTheDocument();
|
|
75
|
-
expect(screen.getByText('Mon')).toBeInTheDocument();
|
|
76
|
-
expect(screen.getByText('Sat')).toBeInTheDocument();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('renders with undefined events prop', () => {
|
|
80
|
-
render(<CalendarView currentDate={defaultDate} locale="en-US" />);
|
|
81
|
-
expect(screen.getByText('January 2024')).toBeInTheDocument();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// ---------------------------------------------------------------
|
|
86
|
-
// Populated state
|
|
87
|
-
// ---------------------------------------------------------------
|
|
88
|
-
describe('populated state', () => {
|
|
89
|
-
const events: CalendarEvent[] = [
|
|
90
|
-
{
|
|
91
|
-
id: '1',
|
|
92
|
-
title: 'Team Meeting',
|
|
93
|
-
start: new Date(2024, 0, 15, 10, 0),
|
|
94
|
-
end: new Date(2024, 0, 15, 11, 0),
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: '2',
|
|
98
|
-
title: 'Lunch Break',
|
|
99
|
-
start: new Date(2024, 0, 15, 12, 0),
|
|
100
|
-
end: new Date(2024, 0, 15, 13, 0),
|
|
101
|
-
},
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
it('renders events in month view', () => {
|
|
105
|
-
render(
|
|
106
|
-
<CalendarView
|
|
107
|
-
currentDate={defaultDate}
|
|
108
|
-
events={events}
|
|
109
|
-
view="month"
|
|
110
|
-
locale="en-US"
|
|
111
|
-
/>
|
|
112
|
-
);
|
|
113
|
-
expect(screen.getByText('Team Meeting')).toBeInTheDocument();
|
|
114
|
-
expect(screen.getByText('Lunch Break')).toBeInTheDocument();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('fires onEventClick when event is clicked', () => {
|
|
118
|
-
const onClick = vi.fn();
|
|
119
|
-
render(
|
|
120
|
-
<CalendarView
|
|
121
|
-
currentDate={defaultDate}
|
|
122
|
-
events={events}
|
|
123
|
-
view="month"
|
|
124
|
-
locale="en-US"
|
|
125
|
-
onEventClick={onClick}
|
|
126
|
-
/>
|
|
127
|
-
);
|
|
128
|
-
fireEvent.click(screen.getByText('Team Meeting'));
|
|
129
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
130
|
-
expect(onClick).toHaveBeenCalledWith(events[0]);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// ---------------------------------------------------------------
|
|
135
|
-
// Navigation
|
|
136
|
-
// ---------------------------------------------------------------
|
|
137
|
-
describe('navigation', () => {
|
|
138
|
-
it('navigates to previous month', () => {
|
|
139
|
-
const onNavigate = vi.fn();
|
|
140
|
-
render(
|
|
141
|
-
<CalendarView
|
|
142
|
-
currentDate={defaultDate}
|
|
143
|
-
events={[]}
|
|
144
|
-
locale="en-US"
|
|
145
|
-
onNavigate={onNavigate}
|
|
146
|
-
/>
|
|
147
|
-
);
|
|
148
|
-
// Click prev button (first icon button after Today)
|
|
149
|
-
const buttons = screen.getAllByRole('button');
|
|
150
|
-
// Find the prev chevron - it's the button after Today
|
|
151
|
-
const todayIdx = buttons.findIndex(b => b.textContent === 'Today');
|
|
152
|
-
fireEvent.click(buttons[todayIdx + 1]);
|
|
153
|
-
expect(onNavigate).toHaveBeenCalledTimes(1);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('navigates to today', () => {
|
|
157
|
-
const onNavigate = vi.fn();
|
|
158
|
-
render(
|
|
159
|
-
<CalendarView
|
|
160
|
-
currentDate={new Date(2024, 5, 15)}
|
|
161
|
-
events={[]}
|
|
162
|
-
locale="en-US"
|
|
163
|
-
onNavigate={onNavigate}
|
|
164
|
-
/>
|
|
165
|
-
);
|
|
166
|
-
fireEvent.click(screen.getByText('Today'));
|
|
167
|
-
expect(onNavigate).toHaveBeenCalledTimes(1);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// ---------------------------------------------------------------
|
|
172
|
-
// View modes
|
|
173
|
-
// ---------------------------------------------------------------
|
|
174
|
-
describe('view modes', () => {
|
|
175
|
-
it('renders month view by default', () => {
|
|
176
|
-
render(<CalendarView currentDate={defaultDate} events={[]} locale="en-US" />);
|
|
177
|
-
// Month view has day-of-week headers
|
|
178
|
-
expect(screen.getByText('Sun')).toBeInTheDocument();
|
|
179
|
-
expect(screen.getByText('Mon')).toBeInTheDocument();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('renders week view', () => {
|
|
183
|
-
render(
|
|
184
|
-
<CalendarView
|
|
185
|
-
currentDate={defaultDate}
|
|
186
|
-
events={[]}
|
|
187
|
-
view="week"
|
|
188
|
-
locale="en-US"
|
|
189
|
-
/>
|
|
190
|
-
);
|
|
191
|
-
// Week view should show a range header and a 7-column grid
|
|
192
|
-
const grids = screen.getAllByRole('grid');
|
|
193
|
-
expect(grids.length).toBeGreaterThanOrEqual(1);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('renders day view', () => {
|
|
197
|
-
render(
|
|
198
|
-
<CalendarView
|
|
199
|
-
currentDate={defaultDate}
|
|
200
|
-
events={[]}
|
|
201
|
-
view="day"
|
|
202
|
-
locale="en-US"
|
|
203
|
-
/>
|
|
204
|
-
);
|
|
205
|
-
// Day view shows time slots
|
|
206
|
-
expect(screen.getByText('12 AM')).toBeInTheDocument();
|
|
207
|
-
expect(screen.getByText('12 PM')).toBeInTheDocument();
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// ---------------------------------------------------------------
|
|
212
|
-
// Edge cases
|
|
213
|
-
// ---------------------------------------------------------------
|
|
214
|
-
describe('edge cases', () => {
|
|
215
|
-
it('handles event spanning multiple days', () => {
|
|
216
|
-
const multiDayEvent: CalendarEvent[] = [
|
|
217
|
-
{
|
|
218
|
-
id: 'multi',
|
|
219
|
-
title: 'Conference',
|
|
220
|
-
start: new Date(2024, 0, 15),
|
|
221
|
-
end: new Date(2024, 0, 17),
|
|
222
|
-
allDay: true,
|
|
223
|
-
},
|
|
224
|
-
];
|
|
225
|
-
render(
|
|
226
|
-
<CalendarView
|
|
227
|
-
currentDate={defaultDate}
|
|
228
|
-
events={multiDayEvent}
|
|
229
|
-
view="month"
|
|
230
|
-
locale="en-US"
|
|
231
|
-
/>
|
|
232
|
-
);
|
|
233
|
-
// Multi-day events render on each day they span
|
|
234
|
-
const elements = screen.getAllByText('Conference');
|
|
235
|
-
expect(elements.length).toBeGreaterThanOrEqual(1);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('handles event with color', () => {
|
|
239
|
-
const coloredEvents: CalendarEvent[] = [
|
|
240
|
-
{
|
|
241
|
-
id: 'red',
|
|
242
|
-
title: 'Red Event',
|
|
243
|
-
start: new Date(2024, 0, 15, 9, 0),
|
|
244
|
-
color: 'bg-red-500 text-white',
|
|
245
|
-
},
|
|
246
|
-
];
|
|
247
|
-
render(
|
|
248
|
-
<CalendarView
|
|
249
|
-
currentDate={defaultDate}
|
|
250
|
-
events={coloredEvents}
|
|
251
|
-
view="month"
|
|
252
|
-
locale="en-US"
|
|
253
|
-
/>
|
|
254
|
-
);
|
|
255
|
-
expect(screen.getByText('Red Event')).toBeInTheDocument();
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('renders onAddClick button when provided', () => {
|
|
259
|
-
const onAdd = vi.fn();
|
|
260
|
-
render(
|
|
261
|
-
<CalendarView
|
|
262
|
-
currentDate={defaultDate}
|
|
263
|
-
events={[]}
|
|
264
|
-
locale="en-US"
|
|
265
|
-
onAddClick={onAdd}
|
|
266
|
-
/>
|
|
267
|
-
);
|
|
268
|
-
// The "New event" button should be visible
|
|
269
|
-
const newButton = screen.getByText('New event');
|
|
270
|
-
expect(newButton).toBeInTheDocument();
|
|
271
|
-
fireEvent.click(newButton);
|
|
272
|
-
expect(onAdd).toHaveBeenCalledTimes(1);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('does not render New button without onAddClick', () => {
|
|
276
|
-
render(
|
|
277
|
-
<CalendarView
|
|
278
|
-
currentDate={defaultDate}
|
|
279
|
-
events={[]}
|
|
280
|
-
locale="en-US"
|
|
281
|
-
/>
|
|
282
|
-
);
|
|
283
|
-
expect(screen.queryByText('New event')).not.toBeInTheDocument();
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('accepts className prop', () => {
|
|
287
|
-
const { container } = render(
|
|
288
|
-
<CalendarView
|
|
289
|
-
currentDate={defaultDate}
|
|
290
|
-
events={[]}
|
|
291
|
-
locale="en-US"
|
|
292
|
-
className="my-calendar"
|
|
293
|
-
/>
|
|
294
|
-
);
|
|
295
|
-
expect(container.firstElementChild!.className).toContain('my-calendar');
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('handles many events without crashing', () => {
|
|
299
|
-
const manyEvents: CalendarEvent[] = Array.from({ length: 100 }, (_, i) => ({
|
|
300
|
-
id: String(i),
|
|
301
|
-
title: `Event ${i}`,
|
|
302
|
-
start: new Date(2024, 0, (i % 28) + 1, 9, 0),
|
|
303
|
-
}));
|
|
304
|
-
const { container } = render(
|
|
305
|
-
<CalendarView
|
|
306
|
-
currentDate={defaultDate}
|
|
307
|
-
events={manyEvents}
|
|
308
|
-
view="month"
|
|
309
|
-
locale="en-US"
|
|
310
|
-
/>
|
|
311
|
-
);
|
|
312
|
-
expect(container).toBeInTheDocument();
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('handles date at month boundary (last day of month)', () => {
|
|
316
|
-
const boundaryDate = new Date(2024, 0, 31); // Jan 31, 2024
|
|
317
|
-
render(
|
|
318
|
-
<CalendarView
|
|
319
|
-
currentDate={boundaryDate}
|
|
320
|
-
events={[]}
|
|
321
|
-
view="month"
|
|
322
|
-
locale="en-US"
|
|
323
|
-
/>
|
|
324
|
-
);
|
|
325
|
-
expect(screen.getByText('January 2024')).toBeInTheDocument();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it('handles date at month boundary (first day of month)', () => {
|
|
329
|
-
const firstDay = new Date(2024, 1, 1); // Feb 1, 2024
|
|
330
|
-
render(
|
|
331
|
-
<CalendarView
|
|
332
|
-
currentDate={firstDay}
|
|
333
|
-
events={[]}
|
|
334
|
-
view="month"
|
|
335
|
-
locale="en-US"
|
|
336
|
-
/>
|
|
337
|
-
);
|
|
338
|
-
expect(screen.getByText('February 2024')).toBeInTheDocument();
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it('handles leap year date', () => {
|
|
342
|
-
const leapDate = new Date(2024, 1, 29); // Feb 29, 2024 (leap year)
|
|
343
|
-
render(
|
|
344
|
-
<CalendarView
|
|
345
|
-
currentDate={leapDate}
|
|
346
|
-
events={[]}
|
|
347
|
-
view="month"
|
|
348
|
-
locale="en-US"
|
|
349
|
-
/>
|
|
350
|
-
);
|
|
351
|
-
expect(screen.getByText('February 2024')).toBeInTheDocument();
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('handles events spanning across month boundary', () => {
|
|
355
|
-
const crossMonthEvent: CalendarEvent[] = [
|
|
356
|
-
{
|
|
357
|
-
id: 'cross',
|
|
358
|
-
title: 'Cross-Month Event',
|
|
359
|
-
start: new Date(2024, 0, 30),
|
|
360
|
-
end: new Date(2024, 1, 2),
|
|
361
|
-
allDay: true,
|
|
362
|
-
},
|
|
363
|
-
];
|
|
364
|
-
const { container } = render(
|
|
365
|
-
<CalendarView
|
|
366
|
-
currentDate={new Date(2024, 0, 15)}
|
|
367
|
-
events={crossMonthEvent}
|
|
368
|
-
view="month"
|
|
369
|
-
locale="en-US"
|
|
370
|
-
/>
|
|
371
|
-
);
|
|
372
|
-
// Should render without crashing; the event spans into next month padding days
|
|
373
|
-
const elements = container.querySelectorAll('[role="button"]');
|
|
374
|
-
expect(elements.length).toBeGreaterThanOrEqual(1);
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
});
|