Skip to content

Commit eb91925

Browse files
Copilothotlong
andcommitted
Add test file for timeline renderer component (packages/plugin-timeline/src/__tests__/renderer.test.tsx)
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 9e964d1 commit eb91925

1 file changed

Lines changed: 295 additions & 0 deletions

File tree

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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+
import React from 'react';
10+
import { describe, it, expect, vi, beforeEach } from 'vitest';
11+
import { render, screen } from '@testing-library/react';
12+
import '@testing-library/jest-dom';
13+
import { ComponentRegistry } from '@object-ui/core';
14+
15+
// Import renderer to trigger registration
16+
import '../renderer';
17+
18+
// Mock renderChildren used by the renderer, preserving cn and other exports
19+
vi.mock('@object-ui/components', async (importOriginal) => {
20+
const actual = await importOriginal<typeof import('@object-ui/components')>();
21+
return {
22+
...actual,
23+
renderChildren: vi.fn(() => null),
24+
};
25+
});
26+
27+
describe('Timeline Renderer (ComponentRegistry)', () => {
28+
let TimelineComponent: any;
29+
30+
beforeEach(() => {
31+
TimelineComponent = ComponentRegistry.get('timeline');
32+
});
33+
34+
it('is registered in ComponentRegistry as "timeline"', () => {
35+
expect(TimelineComponent).toBeDefined();
36+
});
37+
38+
// --- Vertical variant ---
39+
40+
it('renders in vertical variant by default', () => {
41+
const schema: any = {
42+
type: 'timeline',
43+
items: [
44+
{ time: '2024-01-15', title: 'First Event', description: 'Description one' },
45+
],
46+
};
47+
48+
const { container } = render(<TimelineComponent schema={schema} />);
49+
// Vertical uses an <ol> element
50+
expect(container.querySelector('ol')).toBeInTheDocument();
51+
expect(screen.getByText('First Event')).toBeInTheDocument();
52+
expect(screen.getByText('Description one')).toBeInTheDocument();
53+
});
54+
55+
it('renders items with titles and descriptions in vertical variant', () => {
56+
const schema: any = {
57+
type: 'timeline',
58+
variant: 'vertical',
59+
items: [
60+
{ time: '2024-01-15', title: 'Alpha Release', description: 'Initial alpha' },
61+
{ time: '2024-02-01', title: 'Beta Release', description: 'Public beta' },
62+
{ time: '2024-03-10', title: 'GA Launch', description: 'General availability' },
63+
],
64+
};
65+
66+
render(<TimelineComponent schema={schema} />);
67+
expect(screen.getByText('Alpha Release')).toBeInTheDocument();
68+
expect(screen.getByText('Initial alpha')).toBeInTheDocument();
69+
expect(screen.getByText('Beta Release')).toBeInTheDocument();
70+
expect(screen.getByText('Public beta')).toBeInTheDocument();
71+
expect(screen.getByText('GA Launch')).toBeInTheDocument();
72+
expect(screen.getByText('General availability')).toBeInTheDocument();
73+
});
74+
75+
it('handles empty items array in vertical variant', () => {
76+
const schema: any = {
77+
type: 'timeline',
78+
variant: 'vertical',
79+
items: [],
80+
};
81+
82+
const { container } = render(<TimelineComponent schema={schema} />);
83+
// Should render the <ol> wrapper but no <li> items
84+
expect(container.querySelector('ol')).toBeInTheDocument();
85+
expect(container.querySelectorAll('li')).toHaveLength(0);
86+
});
87+
88+
it('formats dates correctly with short format', () => {
89+
const schema: any = {
90+
type: 'timeline',
91+
variant: 'vertical',
92+
dateFormat: 'short',
93+
items: [
94+
{ time: '2024-06-15', title: 'Event' },
95+
],
96+
};
97+
98+
render(<TimelineComponent schema={schema} />);
99+
// short format uses toLocaleDateString()
100+
const expected = new Date('2024-06-15').toLocaleDateString();
101+
expect(screen.getByText(expected)).toBeInTheDocument();
102+
});
103+
104+
it('formats dates correctly with long format', () => {
105+
const schema: any = {
106+
type: 'timeline',
107+
variant: 'vertical',
108+
dateFormat: 'long',
109+
items: [
110+
{ time: '2024-06-15', title: 'Event' },
111+
],
112+
};
113+
114+
render(<TimelineComponent schema={schema} />);
115+
const expected = new Date('2024-06-15').toLocaleDateString('en-US', {
116+
year: 'numeric',
117+
month: 'long',
118+
day: 'numeric',
119+
});
120+
expect(screen.getByText(expected)).toBeInTheDocument();
121+
});
122+
123+
it('formats dates correctly with iso (default) format', () => {
124+
const schema: any = {
125+
type: 'timeline',
126+
variant: 'vertical',
127+
dateFormat: 'iso',
128+
items: [
129+
{ time: '2024-06-15', title: 'Event' },
130+
],
131+
};
132+
133+
render(<TimelineComponent schema={schema} />);
134+
expect(screen.getByText('2024-06-15')).toBeInTheDocument();
135+
});
136+
137+
// --- Horizontal variant ---
138+
139+
it('renders in horizontal variant', () => {
140+
const schema: any = {
141+
type: 'timeline',
142+
variant: 'horizontal',
143+
items: [
144+
{ time: '2024-01-01', title: 'Q1', description: 'First quarter' },
145+
{ time: '2024-04-01', title: 'Q2', description: 'Second quarter' },
146+
],
147+
};
148+
149+
render(<TimelineComponent schema={schema} />);
150+
expect(screen.getByText('Q1')).toBeInTheDocument();
151+
expect(screen.getByText('First quarter')).toBeInTheDocument();
152+
expect(screen.getByText('Q2')).toBeInTheDocument();
153+
expect(screen.getByText('Second quarter')).toBeInTheDocument();
154+
});
155+
156+
it('handles empty items array in horizontal variant', () => {
157+
const schema: any = {
158+
type: 'timeline',
159+
variant: 'horizontal',
160+
items: [],
161+
};
162+
163+
const { container } = render(<TimelineComponent schema={schema} />);
164+
// Horizontal wraps in a div, should have no child items
165+
expect(container.firstChild).toBeInTheDocument();
166+
expect(screen.queryByRole('heading')).not.toBeInTheDocument();
167+
});
168+
169+
// --- Gantt variant ---
170+
171+
it('renders in gantt variant', () => {
172+
const schema: any = {
173+
type: 'timeline',
174+
variant: 'gantt',
175+
timeScale: 'month',
176+
items: [
177+
{
178+
label: 'Backend',
179+
items: [
180+
{ title: 'API Design', startDate: '2024-01-01', endDate: '2024-02-01' },
181+
],
182+
},
183+
{
184+
label: 'Frontend',
185+
items: [
186+
{ title: 'UI Build', startDate: '2024-02-01', endDate: '2024-03-01' },
187+
],
188+
},
189+
],
190+
};
191+
192+
render(<TimelineComponent schema={schema} />);
193+
expect(screen.getByText('Backend')).toBeInTheDocument();
194+
expect(screen.getByText('Frontend')).toBeInTheDocument();
195+
expect(screen.getByText('API Design')).toBeInTheDocument();
196+
expect(screen.getByText('UI Build')).toBeInTheDocument();
197+
});
198+
199+
it('handles rows with no sub-items in gantt variant', () => {
200+
const schema: any = {
201+
type: 'timeline',
202+
variant: 'gantt',
203+
minDate: '2024-01-01',
204+
maxDate: '2024-03-01',
205+
items: [
206+
{ label: 'Empty Row', items: [] },
207+
{
208+
label: 'Has Items',
209+
items: [
210+
{ title: 'Task', startDate: '2024-01-15', endDate: '2024-02-15' },
211+
],
212+
},
213+
],
214+
};
215+
216+
render(<TimelineComponent schema={schema} />);
217+
expect(screen.getByText('Empty Row')).toBeInTheDocument();
218+
expect(screen.getByText('Has Items')).toBeInTheDocument();
219+
});
220+
221+
it('gantt variant calculates date ranges from items', () => {
222+
const schema: any = {
223+
type: 'timeline',
224+
variant: 'gantt',
225+
timeScale: 'month',
226+
items: [
227+
{
228+
label: 'Project A',
229+
items: [
230+
{ title: 'Task 1', startDate: '2024-01-01', endDate: '2024-01-31' },
231+
{ title: 'Task 2', startDate: '2024-02-01', endDate: '2024-03-31' },
232+
],
233+
},
234+
],
235+
};
236+
237+
render(<TimelineComponent schema={schema} />);
238+
// Gantt header should contain month labels spanning the range
239+
expect(screen.getByText('Project A')).toBeInTheDocument();
240+
expect(screen.getByText('Task 1')).toBeInTheDocument();
241+
expect(screen.getByText('Task 2')).toBeInTheDocument();
242+
});
243+
244+
it('gantt variant uses custom rowLabel', () => {
245+
const schema: any = {
246+
type: 'timeline',
247+
variant: 'gantt',
248+
rowLabel: 'Projects',
249+
minDate: '2024-01-01',
250+
maxDate: '2024-03-01',
251+
items: [
252+
{
253+
label: 'My Project',
254+
items: [
255+
{ title: 'Work', startDate: '2024-01-15', endDate: '2024-02-15' },
256+
],
257+
},
258+
],
259+
};
260+
261+
render(<TimelineComponent schema={schema} />);
262+
expect(screen.getByText('Projects')).toBeInTheDocument();
263+
});
264+
265+
it('gantt variant defaults rowLabel to "Items"', () => {
266+
const schema: any = {
267+
type: 'timeline',
268+
variant: 'gantt',
269+
minDate: '2024-01-01',
270+
maxDate: '2024-03-01',
271+
items: [
272+
{
273+
label: 'Row',
274+
items: [
275+
{ title: 'Bar', startDate: '2024-01-10', endDate: '2024-02-10' },
276+
],
277+
},
278+
],
279+
};
280+
281+
render(<TimelineComponent schema={schema} />);
282+
expect(screen.getByText('Items')).toBeInTheDocument();
283+
});
284+
285+
it('returns null for unknown variant', () => {
286+
const schema: any = {
287+
type: 'timeline',
288+
variant: 'unknown',
289+
items: [],
290+
};
291+
292+
const { container } = render(<TimelineComponent schema={schema} />);
293+
expect(container.innerHTML).toBe('');
294+
});
295+
});

0 commit comments

Comments
 (0)