@object-ui/plugin-calendar 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 (37) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +21 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +27 -17
  5. package/dist/index.umd.cjs +2 -2
  6. package/dist/packages/plugin-calendar/src/CalendarView.d.ts.map +1 -0
  7. package/dist/packages/plugin-calendar/src/ObjectCalendar.d.ts.map +1 -0
  8. package/dist/packages/plugin-calendar/src/calendar-view-renderer.d.ts.map +1 -0
  9. package/dist/packages/plugin-calendar/src/index.d.ts.map +1 -0
  10. package/package.json +37 -14
  11. package/.turbo/turbo-build.log +0 -22
  12. package/dist/src/CalendarView.d.ts.map +0 -1
  13. package/dist/src/ObjectCalendar.d.ts.map +0 -1
  14. package/dist/src/calendar-view-renderer.d.ts.map +0 -1
  15. package/dist/src/index.d.ts.map +0 -1
  16. package/src/CalendarView.test.tsx +0 -118
  17. package/src/CalendarView.tsx +0 -821
  18. package/src/ObjectCalendar.msw.test.tsx +0 -100
  19. package/src/ObjectCalendar.stories.tsx +0 -82
  20. package/src/ObjectCalendar.tsx +0 -420
  21. package/src/__tests__/accessibility.test.tsx +0 -290
  22. package/src/__tests__/calendar-bugfixes.test.tsx +0 -230
  23. package/src/__tests__/calendar-optimizations.test.tsx +0 -178
  24. package/src/__tests__/performance-benchmark.test.tsx +0 -227
  25. package/src/__tests__/view-states.test.tsx +0 -377
  26. package/src/calendar-view-renderer.tsx +0 -181
  27. package/src/index.tsx +0 -50
  28. package/src/registration.test.tsx +0 -41
  29. package/test/setup.ts +0 -32
  30. package/tsconfig.json +0 -18
  31. package/vite.config.ts +0 -56
  32. package/vitest.config.ts +0 -13
  33. package/vitest.setup.ts +0 -1
  34. /package/dist/{src → packages/plugin-calendar/src}/CalendarView.d.ts +0 -0
  35. /package/dist/{src → packages/plugin-calendar/src}/ObjectCalendar.d.ts +0 -0
  36. /package/dist/{src → packages/plugin-calendar/src}/calendar-view-renderer.d.ts +0 -0
  37. /package/dist/{src → packages/plugin-calendar/src}/index.d.ts +0 -0
@@ -1,290 +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
- * Screen reader experience tests for CalendarView.
11
- *
12
- * Tests ARIA attributes, roles, landmarks, keyboard navigation,
13
- * and screen reader announcements for the calendar plugin.
14
- * Part of P2.3 Accessibility & Inclusive Design roadmap.
15
- */
16
-
17
- import { describe, it, expect, vi } from 'vitest';
18
- import { render, screen, fireEvent } from '@testing-library/react';
19
- import '@testing-library/jest-dom';
20
- import React from 'react';
21
- import { CalendarView, type CalendarEvent } from '../CalendarView';
22
-
23
- // Mock ResizeObserver
24
- class ResizeObserver {
25
- observe() {}
26
- unobserve() {}
27
- disconnect() {}
28
- }
29
- global.ResizeObserver = ResizeObserver;
30
-
31
- // Mock PointerEvents for Radix
32
- if (!global.PointerEvent) {
33
- class PointerEvent extends Event {
34
- button: number;
35
- ctrlKey: boolean;
36
- metaKey: boolean;
37
- shiftKey: boolean;
38
- constructor(type: string, props: any = {}) {
39
- super(type, props);
40
- this.button = props.button || 0;
41
- this.ctrlKey = props.ctrlKey || false;
42
- this.metaKey = props.metaKey || false;
43
- this.shiftKey = props.shiftKey || false;
44
- }
45
- }
46
- // @ts-expect-error Mocking global PointerEvent
47
- global.PointerEvent = PointerEvent as any;
48
- }
49
-
50
- // Mock HTMLElement.offsetParent for Radix Popper
51
- Object.defineProperty(HTMLElement.prototype, 'offsetParent', {
52
- get() {
53
- return this.parentElement;
54
- },
55
- });
56
-
57
- const defaultDate = new Date(2024, 0, 15); // Jan 15, 2024
58
-
59
- const mockEvents: CalendarEvent[] = [
60
- {
61
- id: '1',
62
- title: 'Team Standup',
63
- start: new Date(2024, 0, 15, 9, 0),
64
- end: new Date(2024, 0, 15, 9, 30),
65
- },
66
- {
67
- id: '2',
68
- title: 'Sprint Review',
69
- start: new Date(2024, 0, 15, 14, 0),
70
- end: new Date(2024, 0, 15, 15, 0),
71
- },
72
- {
73
- id: '3',
74
- title: 'All Day Workshop',
75
- start: new Date(2024, 0, 16, 0, 0),
76
- end: new Date(2024, 0, 16, 23, 59),
77
- allDay: true,
78
- },
79
- ];
80
-
81
- describe('CalendarView: Screen Reader & Accessibility', () => {
82
- describe('header navigation controls', () => {
83
- it('renders navigation buttons with accessible labels', () => {
84
- render(<CalendarView currentDate={defaultDate} locale="en-US" />);
85
-
86
- const buttons = screen.getAllByRole('button');
87
- // Should have: Today, Prev, Next, Date Picker Trigger, and View Switcher
88
- expect(buttons.length).toBeGreaterThanOrEqual(4);
89
- });
90
-
91
- it('Today button is clearly labeled', () => {
92
- render(<CalendarView currentDate={defaultDate} locale="en-US" />);
93
-
94
- const todayButton = screen.getByText('Today');
95
- expect(todayButton).toBeInTheDocument();
96
- expect(todayButton.closest('button')).toBeInTheDocument();
97
- });
98
-
99
- it('date label is inside an interactive element', () => {
100
- render(<CalendarView currentDate={defaultDate} locale="en-US" />);
101
-
102
- const dateLabel = screen.getByText('January 2024');
103
- expect(dateLabel).toBeInTheDocument();
104
-
105
- const triggerButton = dateLabel.closest('button');
106
- expect(triggerButton).toBeInTheDocument();
107
- });
108
-
109
- it('previous and next navigation buttons exist', () => {
110
- render(<CalendarView currentDate={defaultDate} locale="en-US" />);
111
-
112
- const buttons = screen.getAllByRole('button');
113
- // At least Today, Prev, Next buttons
114
- expect(buttons.length).toBeGreaterThanOrEqual(3);
115
- });
116
- });
117
-
118
- describe('view switcher accessibility', () => {
119
- it('view switcher displays current view', () => {
120
- render(<CalendarView currentDate={defaultDate} view="month" locale="en-US" />);
121
-
122
- const monthText = screen.getByText('Month');
123
- expect(monthText).toBeInTheDocument();
124
- });
125
-
126
- it('view switcher is inside a button trigger', () => {
127
- render(<CalendarView currentDate={defaultDate} view="month" locale="en-US" />);
128
-
129
- const monthText = screen.getByText('Month');
130
- const trigger = monthText.closest('button');
131
- expect(trigger).toBeInTheDocument();
132
- });
133
-
134
- it('week view renders without error when locale is available', () => {
135
- // Note: WeekView has a known locale scoping issue in CalendarView.tsx.
136
- // This test verifies the month view switcher text is present before switching.
137
- render(<CalendarView currentDate={defaultDate} view="month" locale="en-US" />);
138
-
139
- const monthText = screen.getByText('Month');
140
- expect(monthText).toBeInTheDocument();
141
- });
142
-
143
- it('day view renders accessible buttons', () => {
144
- // Note: DayView has a known locale scoping issue in CalendarView.tsx.
145
- // Testing button accessibility with the default month view instead.
146
- render(<CalendarView currentDate={defaultDate} view="month" locale="en-US" />);
147
-
148
- const buttons = screen.getAllByRole('button');
149
- expect(buttons.length).toBeGreaterThanOrEqual(3);
150
- });
151
- });
152
-
153
- describe('calendar grid structure', () => {
154
- it('month view renders day-of-week headers', () => {
155
- render(<CalendarView currentDate={defaultDate} view="month" locale="en-US" />);
156
-
157
- // Day headers (Sun, Mon, Tue, etc.)
158
- expect(screen.getByText('Sun')).toBeInTheDocument();
159
- expect(screen.getByText('Mon')).toBeInTheDocument();
160
- expect(screen.getByText('Tue')).toBeInTheDocument();
161
- expect(screen.getByText('Wed')).toBeInTheDocument();
162
- expect(screen.getByText('Thu')).toBeInTheDocument();
163
- expect(screen.getByText('Fri')).toBeInTheDocument();
164
- expect(screen.getByText('Sat')).toBeInTheDocument();
165
- });
166
-
167
- it('month view renders day numbers', () => {
168
- render(<CalendarView currentDate={defaultDate} view="month" locale="en-US" />);
169
-
170
- // Should have day 15 (current date)
171
- const day15 = screen.getAllByText('15');
172
- expect(day15.length).toBeGreaterThanOrEqual(1);
173
- });
174
-
175
- it('events are rendered within the calendar', () => {
176
- render(
177
- <CalendarView
178
- currentDate={defaultDate}
179
- events={mockEvents}
180
- view="month"
181
- locale="en-US"
182
- />
183
- );
184
-
185
- expect(screen.getByText('Team Standup')).toBeInTheDocument();
186
- expect(screen.getByText('Sprint Review')).toBeInTheDocument();
187
- });
188
- });
189
-
190
- describe('interactive behaviors', () => {
191
- it('navigation buttons are clickable and functional', () => {
192
- const onNavigate = vi.fn();
193
- render(
194
- <CalendarView
195
- currentDate={defaultDate}
196
- onNavigate={onNavigate}
197
- locale="en-US"
198
- />
199
- );
200
-
201
- const todayButton = screen.getByText('Today');
202
- fireEvent.click(todayButton);
203
-
204
- expect(onNavigate).toHaveBeenCalled();
205
- });
206
-
207
- it('event click handler is supported', () => {
208
- const onEventClick = vi.fn();
209
- render(
210
- <CalendarView
211
- currentDate={defaultDate}
212
- events={mockEvents}
213
- onEventClick={onEventClick}
214
- view="month"
215
- locale="en-US"
216
- />
217
- );
218
-
219
- const event = screen.getByText('Team Standup');
220
- fireEvent.click(event);
221
-
222
- expect(onEventClick).toHaveBeenCalled();
223
- });
224
-
225
- it('date picker trigger is accessible', () => {
226
- render(<CalendarView currentDate={defaultDate} locale="en-US" />);
227
-
228
- const dateLabel = screen.getByText('January 2024');
229
- const trigger = dateLabel.closest('button');
230
- expect(trigger).toBeEnabled();
231
- });
232
- });
233
-
234
- describe('semantic calendar markup', () => {
235
- it('calendar root has proper CSS structure', () => {
236
- const { container } = render(
237
- <CalendarView currentDate={defaultDate} locale="en-US" />
238
- );
239
-
240
- // Root element should have flex layout
241
- const root = container.firstElementChild as HTMLElement;
242
- expect(root).toHaveClass('flex', 'flex-col');
243
- });
244
-
245
- it('header section is separated by border', () => {
246
- const { container } = render(
247
- <CalendarView currentDate={defaultDate} locale="en-US" />
248
- );
249
-
250
- const header = container.querySelector('.border-b');
251
- expect(header).toBeInTheDocument();
252
- });
253
-
254
- it('custom className is applied to root', () => {
255
- const { container } = render(
256
- <CalendarView
257
- currentDate={defaultDate}
258
- className="custom-calendar"
259
- locale="en-US"
260
- />
261
- );
262
-
263
- const root = container.firstElementChild as HTMLElement;
264
- expect(root.className).toContain('custom-calendar');
265
- });
266
- });
267
-
268
- describe('add event action', () => {
269
- it('add button renders when onAddClick is provided', () => {
270
- const onAddClick = vi.fn();
271
- render(
272
- <CalendarView
273
- currentDate={defaultDate}
274
- onAddClick={onAddClick}
275
- locale="en-US"
276
- />
277
- );
278
-
279
- const newButton = screen.getByText('New event');
280
- expect(newButton).toBeInTheDocument();
281
- expect(newButton.closest('button')).toBeInTheDocument();
282
- });
283
-
284
- it('add button is not shown when onAddClick is absent', () => {
285
- render(<CalendarView currentDate={defaultDate} locale="en-US" />);
286
-
287
- expect(screen.queryByText('New event')).not.toBeInTheDocument();
288
- });
289
- });
290
- });
@@ -1,230 +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
- * Tests for calendar bug fixes:
11
- * - Event click always dispatches action (no schema.onEventClick guard)
12
- * - i18n integration with useObjectTranslation
13
- * - Tooltip (title attribute) on truncated event titles
14
- * - Cross-month date visual distinction (opacity)
15
- * - Today highlight spacing improvement
16
- */
17
-
18
- import { describe, it, expect, vi } from 'vitest';
19
- import { render, screen, fireEvent } from '@testing-library/react';
20
- import '@testing-library/jest-dom';
21
- import React from 'react';
22
- import { CalendarView, type CalendarEvent } from '../CalendarView';
23
- import { I18nProvider } from '@object-ui/i18n';
24
-
25
- // Mock ResizeObserver
26
- class ResizeObserver {
27
- observe() {}
28
- unobserve() {}
29
- disconnect() {}
30
- }
31
- global.ResizeObserver = ResizeObserver;
32
-
33
- // Mock PointerEvents for Radix
34
- if (!global.PointerEvent) {
35
- class PointerEvent extends Event {
36
- button: number;
37
- ctrlKey: boolean;
38
- metaKey: boolean;
39
- shiftKey: boolean;
40
- constructor(type: string, props: any = {}) {
41
- super(type, props);
42
- this.button = props.button || 0;
43
- this.ctrlKey = props.ctrlKey || false;
44
- this.metaKey = props.metaKey || false;
45
- this.shiftKey = props.shiftKey || false;
46
- }
47
- }
48
- // @ts-expect-error Mocking global PointerEvent
49
- global.PointerEvent = PointerEvent as any;
50
- }
51
-
52
- // Mock HTMLElement.offsetParent for Radix Popper
53
- Object.defineProperty(HTMLElement.prototype, 'offsetParent', {
54
- get() {
55
- return this.parentElement;
56
- },
57
- });
58
-
59
- const defaultDate = new Date(2024, 0, 15); // Jan 15, 2024
60
-
61
- const sampleEvents: CalendarEvent[] = [
62
- {
63
- id: '1',
64
- title: 'Design dashboard widget layout and structure',
65
- start: new Date(2024, 0, 15, 10, 0),
66
- end: new Date(2024, 0, 15, 11, 0),
67
- color: '#3b82f6',
68
- },
69
- {
70
- id: '2',
71
- title: 'Short Event',
72
- start: new Date(2024, 0, 15, 14, 0),
73
- },
74
- ];
75
-
76
- function renderWithI18n(ui: React.ReactElement, lang = 'en') {
77
- return render(
78
- <I18nProvider config={{ defaultLanguage: lang, detectBrowserLanguage: false }}>
79
- {ui}
80
- </I18nProvider>
81
- );
82
- }
83
-
84
- describe('Calendar Bug Fixes', () => {
85
- describe('event click dispatches action', () => {
86
- it('fires onEventClick when event is clicked in month view', () => {
87
- const onClick = vi.fn();
88
- renderWithI18n(
89
- <CalendarView
90
- currentDate={defaultDate}
91
- events={sampleEvents}
92
- view="month"
93
- onEventClick={onClick}
94
- />
95
- );
96
- fireEvent.click(screen.getByText('Short Event'));
97
- expect(onClick).toHaveBeenCalledTimes(1);
98
- expect(onClick).toHaveBeenCalledWith(sampleEvents[1]);
99
- });
100
- });
101
-
102
- describe('tooltip on truncated event titles', () => {
103
- it('renders title attribute on event cards in month view', () => {
104
- renderWithI18n(
105
- <CalendarView
106
- currentDate={defaultDate}
107
- events={sampleEvents}
108
- view="month"
109
- />
110
- );
111
- const eventEl = screen.getByText('Short Event');
112
- expect(eventEl.closest('[title]')).toHaveAttribute('title', 'Short Event');
113
- });
114
-
115
- it('renders title attribute with long title in month view', () => {
116
- renderWithI18n(
117
- <CalendarView
118
- currentDate={defaultDate}
119
- events={sampleEvents}
120
- view="month"
121
- />
122
- );
123
- const eventEl = screen.getByText('Design dashboard widget layout and structure');
124
- expect(eventEl.closest('[title]')).toHaveAttribute(
125
- 'title',
126
- 'Design dashboard widget layout and structure'
127
- );
128
- });
129
-
130
- it('renders title attribute on event cards in day view', () => {
131
- renderWithI18n(
132
- <CalendarView
133
- currentDate={defaultDate}
134
- events={sampleEvents}
135
- view="day"
136
- />
137
- );
138
- const eventEl = screen.getByText('Short Event');
139
- expect(eventEl.closest('[title]')).toHaveAttribute('title', 'Short Event');
140
- });
141
- });
142
-
143
- describe('i18n integration', () => {
144
- it('renders labels from i18n when language is en', () => {
145
- const onAdd = vi.fn();
146
- renderWithI18n(
147
- <CalendarView
148
- currentDate={defaultDate}
149
- events={[]}
150
- onAddClick={onAdd}
151
- />,
152
- 'en'
153
- );
154
- expect(screen.getByText('Today')).toBeInTheDocument();
155
- expect(screen.getByText('New event')).toBeInTheDocument();
156
- });
157
-
158
- it('renders labels from i18n when language is zh', () => {
159
- const onAdd = vi.fn();
160
- renderWithI18n(
161
- <CalendarView
162
- currentDate={defaultDate}
163
- events={[]}
164
- onAddClick={onAdd}
165
- />,
166
- 'zh'
167
- );
168
- expect(screen.getByText('今天')).toBeInTheDocument();
169
- expect(screen.getByText('新建事件')).toBeInTheDocument();
170
- });
171
-
172
- it('renders locale-aware weekday headers for zh', () => {
173
- renderWithI18n(
174
- <CalendarView
175
- currentDate={defaultDate}
176
- events={[]}
177
- view="month"
178
- />,
179
- 'zh'
180
- );
181
- expect(screen.getByText('周日')).toBeInTheDocument();
182
- expect(screen.getByText('周一')).toBeInTheDocument();
183
- });
184
- });
185
-
186
- describe('cross-month date styling', () => {
187
- it('applies opacity to non-current-month dates', () => {
188
- const { container } = renderWithI18n(
189
- <CalendarView
190
- currentDate={new Date(2024, 1, 15)} // Feb 15, 2024
191
- events={[]}
192
- view="month"
193
- />
194
- );
195
- const gridCells = container.querySelectorAll('[role="gridcell"]');
196
- const firstCell = gridCells[0];
197
- expect(firstCell.className).toContain('opacity-50');
198
- });
199
-
200
- it('does not apply opacity to current-month dates', () => {
201
- const { container } = renderWithI18n(
202
- <CalendarView
203
- currentDate={new Date(2024, 1, 15)} // Feb 15, 2024
204
- events={[]}
205
- view="month"
206
- />
207
- );
208
- const gridCells = container.querySelectorAll('[role="gridcell"]');
209
- // Feb 2024 starts on Thursday, so index 4 (0-based) is Feb 1
210
- const feb1Cell = gridCells[4];
211
- expect(feb1Cell.className).not.toContain('opacity-50');
212
- });
213
- });
214
-
215
- describe('today highlight spacing', () => {
216
- it('renders today date with mb-2 spacing for better separation', () => {
217
- const today = new Date();
218
- const { container } = renderWithI18n(
219
- <CalendarView
220
- currentDate={today}
221
- events={[]}
222
- view="month"
223
- />
224
- );
225
- const todayEl = container.querySelector('[aria-current="date"]');
226
- expect(todayEl).toBeInTheDocument();
227
- expect(todayEl?.className).toContain('mb-2');
228
- });
229
- });
230
- });
@@ -1,178 +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
- * Tests for CalendarView optimizations:
9
- * - Event index (Map-based lookup instead of O(N) per cell)
10
- * - Stable default date reference
11
- * - HEX color text-white fix
12
- * - onEventDrop / locale passthrough from ObjectCalendar
13
- */
14
-
15
- import { describe, it, expect, vi } from 'vitest';
16
- import { render, screen } 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 @object-ui/components
22
- vi.mock('@object-ui/components', () => ({
23
- cn: (...classes: (string | undefined | boolean)[]) => classes.filter(Boolean).join(' '),
24
- Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
25
- Select: ({ children, value, onValueChange }: any) => <div data-testid="select">{children}</div>,
26
- SelectContent: ({ children }: any) => <div>{children}</div>,
27
- SelectItem: ({ children, value }: any) => <div data-value={value}>{children}</div>,
28
- SelectTrigger: ({ children }: any) => <div>{children}</div>,
29
- SelectValue: () => <span>Month</span>,
30
- Calendar: (props: any) => <div data-testid="calendar-picker">Calendar Picker</div>,
31
- Popover: ({ children }: any) => <div>{children}</div>,
32
- PopoverContent: ({ children }: any) => <div>{children}</div>,
33
- PopoverTrigger: ({ children }: any) => <div>{children}</div>,
34
- }));
35
-
36
- // Mock lucide-react icons
37
- vi.mock('lucide-react', () => ({
38
- ChevronLeftIcon: (props: any) => <svg data-testid="chevron-left" {...props} />,
39
- ChevronRightIcon: (props: any) => <svg data-testid="chevron-right" {...props} />,
40
- CalendarIcon: (props: any) => <svg data-testid="calendar-icon" {...props} />,
41
- PlusIcon: (props: any) => <svg data-testid="plus-icon" {...props} />,
42
- }));
43
-
44
- // Mock ResizeObserver
45
- class ResizeObserver {
46
- observe() {}
47
- unobserve() {}
48
- disconnect() {}
49
- }
50
- global.ResizeObserver = ResizeObserver;
51
-
52
- const baseDate = new Date(2024, 0, 15); // Jan 15, 2024
53
-
54
- describe('CalendarView optimizations', () => {
55
- describe('event index (Map-based lookup)', () => {
56
- it('renders single-day events correctly with the new index', () => {
57
- const events: CalendarEvent[] = [
58
- { id: '1', title: 'Morning Meeting', start: new Date(2024, 0, 15, 9, 0), end: new Date(2024, 0, 15, 10, 0) },
59
- { id: '2', title: 'Lunch', start: new Date(2024, 0, 15, 12, 0), end: new Date(2024, 0, 15, 13, 0) },
60
- ];
61
- render(<CalendarView currentDate={baseDate} events={events} view="month" locale="en-US" />);
62
- expect(screen.getByText('Morning Meeting')).toBeInTheDocument();
63
- expect(screen.getByText('Lunch')).toBeInTheDocument();
64
- });
65
-
66
- it('renders multi-day events on all spanned days', () => {
67
- const events: CalendarEvent[] = [
68
- {
69
- id: 'multi-1',
70
- title: 'Conference',
71
- start: new Date(2024, 0, 15, 9, 0),
72
- end: new Date(2024, 0, 17, 17, 0),
73
- allDay: true,
74
- },
75
- ];
76
- const { container } = render(
77
- <CalendarView currentDate={baseDate} events={events} view="month" locale="en-US" />
78
- );
79
- // The multi-day event should appear on Jan 15, 16, and 17 — 3 cells
80
- const eventElements = container.querySelectorAll('[role="button"][aria-label="Conference"]');
81
- expect(eventElements.length).toBe(3);
82
- });
83
-
84
- it('renders events with no end date on their start day only', () => {
85
- const events: CalendarEvent[] = [
86
- { id: 'no-end', title: 'No End Event', start: new Date(2024, 0, 20, 14, 0) },
87
- ];
88
- const { container } = render(
89
- <CalendarView currentDate={baseDate} events={events} view="month" locale="en-US" />
90
- );
91
- const eventElements = container.querySelectorAll('[role="button"][aria-label="No End Event"]');
92
- expect(eventElements.length).toBe(1);
93
- });
94
- });
95
-
96
- describe('HEX color text-white fix', () => {
97
- it('applies text-white class when event color is a HEX value in month view', () => {
98
- const events: CalendarEvent[] = [
99
- { id: 'hex-1', title: 'HEX Event', start: new Date(2024, 0, 15), color: '#3b82f6' },
100
- ];
101
- const { container } = render(
102
- <CalendarView currentDate={baseDate} events={events} view="month" locale="en-US" />
103
- );
104
- const eventEl = container.querySelector('[aria-label="HEX Event"]');
105
- expect(eventEl).toBeInTheDocument();
106
- expect(eventEl!.className).toContain('text-white');
107
- expect((eventEl as HTMLElement).style.backgroundColor).toBe('#3b82f6');
108
- });
109
-
110
- it('uses default color class when event color is a Tailwind class', () => {
111
- const events: CalendarEvent[] = [
112
- { id: 'tw-1', title: 'Tailwind Event', start: new Date(2024, 0, 15), color: 'bg-red-500 text-white' },
113
- ];
114
- const { container } = render(
115
- <CalendarView currentDate={baseDate} events={events} view="month" locale="en-US" />
116
- );
117
- const eventEl = container.querySelector('[aria-label="Tailwind Event"]');
118
- expect(eventEl).toBeInTheDocument();
119
- expect(eventEl!.className).toContain('bg-red-500');
120
- expect(eventEl!.className).toContain('text-white');
121
- });
122
-
123
- it('falls back to DEFAULT_EVENT_COLOR when no color is specified', () => {
124
- const events: CalendarEvent[] = [
125
- { id: 'no-color', title: 'No Color', start: new Date(2024, 0, 15) },
126
- ];
127
- const { container } = render(
128
- <CalendarView currentDate={baseDate} events={events} view="month" locale="en-US" />
129
- );
130
- const eventEl = container.querySelector('[aria-label="No Color"]');
131
- expect(eventEl).toBeInTheDocument();
132
- expect(eventEl!.className).toContain('bg-blue-500');
133
- expect(eventEl!.className).toContain('text-white');
134
- });
135
- });
136
-
137
- describe('stable default date', () => {
138
- it('renders without currentDate prop without errors', () => {
139
- const { container } = render(<CalendarView events={[]} locale="en-US" />);
140
- expect(container).toBeTruthy();
141
- });
142
-
143
- it('does not trigger re-render loop when currentDate is not provided', () => {
144
- const onNavigate = vi.fn();
145
- const { rerender } = render(<CalendarView events={[]} locale="en-US" onNavigate={onNavigate} />);
146
- // Re-render the same component — should NOT trigger onNavigate from the effect
147
- rerender(<CalendarView events={[]} locale="en-US" onNavigate={onNavigate} />);
148
- expect(onNavigate).not.toHaveBeenCalled();
149
- });
150
- });
151
-
152
- describe('drag-and-drop enablement', () => {
153
- it('events are draggable when onEventDrop is provided', () => {
154
- const events: CalendarEvent[] = [
155
- { id: 'd-1', title: 'Drag Event', start: new Date(2024, 0, 15) },
156
- ];
157
- const onEventDrop = vi.fn();
158
- const { container } = render(
159
- <CalendarView currentDate={baseDate} events={events} view="month" onEventDrop={onEventDrop} locale="en-US" />
160
- );
161
- const eventEl = container.querySelector('[aria-label="Drag Event"]');
162
- expect(eventEl).toBeInTheDocument();
163
- expect(eventEl!.getAttribute('draggable')).toBe('true');
164
- });
165
-
166
- it('events are not draggable when onEventDrop is not provided', () => {
167
- const events: CalendarEvent[] = [
168
- { id: 'nd-1', title: 'No Drag Event', start: new Date(2024, 0, 15) },
169
- ];
170
- const { container } = render(
171
- <CalendarView currentDate={baseDate} events={events} view="month" locale="en-US" />
172
- );
173
- const eventEl = container.querySelector('[aria-label="No Drag Event"]');
174
- expect(eventEl).toBeInTheDocument();
175
- expect(eventEl!.getAttribute('draggable')).toBe('false');
176
- });
177
- });
178
- });