Skip to content

Commit 967c221

Browse files
committed
Add tests for ObjectView component, including rendering and view switching
1 parent e37590b commit 967c221

2 files changed

Lines changed: 156 additions & 1 deletion

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { render, screen, fireEvent } from '@testing-library/react';
3+
import '@testing-library/jest-dom';
4+
import { ObjectView } from '../components/ObjectView';
5+
6+
// Mock child plugins to isolate ObjectView logic
7+
vi.mock('@object-ui/plugin-grid', () => ({
8+
ObjectGrid: (props: any) => <div data-testid="object-grid">Grid View: {props.schema.objectName}</div>
9+
}));
10+
11+
vi.mock('@object-ui/plugin-kanban', () => ({
12+
ObjectKanban: (props: any) => <div data-testid="object-kanban">Kanban View: {props.schema.groupBy}</div>
13+
}));
14+
15+
vi.mock('@object-ui/plugin-calendar', () => ({
16+
ObjectCalendar: (props: any) => <div data-testid="object-calendar">Calendar View: {props.schema.dateField}</div>
17+
}));
18+
19+
// Mock UI Components to allow interaction testing without Radix complexity
20+
vi.mock('@object-ui/components', async () => {
21+
return {
22+
Button: ({ children, onClick }: any) => <button onClick={onClick}>{children}</button>,
23+
Tabs: ({ value, onValueChange, children }: any) => (
24+
<div data-testid="tabs" data-value={value} onClick={(e: any) => {
25+
// Simple event delegation for testing
26+
const trigger = (e.target as HTMLElement).closest('[data-tab-value]');
27+
if(trigger) {
28+
const newValue = trigger.getAttribute('data-tab-value');
29+
if (newValue) onValueChange(newValue);
30+
}
31+
}}>
32+
{children}
33+
</div>
34+
),
35+
TabsList: ({ children }: any) => <div data-testid="tabs-list">{children}</div>,
36+
TabsTrigger: ({ value, children }: any) => (
37+
<button data-testid="tabs-trigger" data-tab-value={value}>
38+
{children}
39+
</button>
40+
)
41+
};
42+
});
43+
44+
// Mock React Router
45+
const mockUseParams = vi.fn();
46+
const mockSetSearchParams = vi.fn();
47+
// Default mock implementation
48+
let mockSearchParams = new URLSearchParams();
49+
50+
vi.mock('react-router-dom', () => ({
51+
useParams: () => mockUseParams(),
52+
useSearchParams: () => [mockSearchParams, mockSetSearchParams],
53+
}));
54+
55+
describe('ObjectView Component', () => {
56+
57+
const mockDataSource = {
58+
find: vi.fn().mockResolvedValue([]),
59+
delete: vi.fn().mockResolvedValue(true)
60+
};
61+
62+
const mockObjects = [
63+
{
64+
name: 'opportunity',
65+
label: 'Opportunity',
66+
fields: {
67+
name: { label: 'Name' },
68+
stage: { label: 'Stage' }
69+
},
70+
list_views: {
71+
all: { label: 'All Opportunities', type: 'grid', columns: ['name', 'stage'] },
72+
pipeline: { label: 'Pipeline', type: 'kanban', groupBy: 'stage', columns: ['name'] }
73+
}
74+
},
75+
{
76+
name: 'todo_task',
77+
label: 'Task',
78+
fields: { subject: { label: 'Subject' }, due_date: { label: 'Due' } },
79+
list_views: {
80+
all: { label: 'All Tasks', type: 'grid', columns: ['subject'] },
81+
calendar: { label: 'My Calendar', type: 'calendar', dateField: 'due_date' }
82+
}
83+
}
84+
];
85+
86+
beforeEach(() => {
87+
vi.clearAllMocks();
88+
mockSearchParams = new URLSearchParams(); // Reset params
89+
});
90+
91+
it('renders error when object is not found', () => {
92+
mockUseParams.mockReturnValue({ objectName: 'unknown_object' });
93+
94+
render(<ObjectView dataSource={mockDataSource} objects={mockObjects} onEdit={vi.fn()} />);
95+
96+
expect(screen.getByText('Object Not Found')).toBeInTheDocument();
97+
});
98+
99+
it('renders default grid view for Opportunity', () => {
100+
mockUseParams.mockReturnValue({ objectName: 'opportunity' });
101+
102+
render(<ObjectView dataSource={mockDataSource} objects={mockObjects} onEdit={vi.fn()} />);
103+
104+
// Check Header
105+
expect(screen.getByText('Opportunity')).toBeInTheDocument();
106+
107+
// Check Tabs exist
108+
expect(screen.getByText('All Opportunities')).toBeInTheDocument();
109+
expect(screen.getByText('Pipeline')).toBeInTheDocument();
110+
111+
// Check Grid is rendered (default)
112+
expect(screen.getByTestId('object-grid')).toBeInTheDocument();
113+
expect(screen.getByText('Grid View: opportunity')).toBeInTheDocument();
114+
expect(screen.queryByTestId('object-kanban')).not.toBeInTheDocument();
115+
});
116+
117+
it('switches to Kanban view when tab is active', () => {
118+
mockUseParams.mockReturnValue({ objectName: 'opportunity' });
119+
// Simulate "view=pipeline" in URL
120+
mockSearchParams.set('view', 'pipeline');
121+
122+
render(<ObjectView dataSource={mockDataSource} objects={mockObjects} onEdit={vi.fn()} />);
123+
124+
// Grid should be gone, Kanban should be present
125+
expect(screen.queryByTestId('object-grid')).not.toBeInTheDocument();
126+
expect(screen.getByTestId('object-kanban')).toBeInTheDocument();
127+
expect(screen.getByText('Kanban View: stage')).toBeInTheDocument();
128+
});
129+
130+
it('fires search param update when tab is clicked', () => {
131+
mockUseParams.mockReturnValue({ objectName: 'opportunity' });
132+
133+
render(<ObjectView dataSource={mockDataSource} objects={mockObjects} onEdit={vi.fn()} />);
134+
135+
// Find and click the Pipeline tab
136+
const pipelineTab = screen.getByText('Pipeline');
137+
fireEvent.click(pipelineTab);
138+
139+
// Should call setSearchParams with new view
140+
expect(mockSetSearchParams).toHaveBeenCalledWith({ view: 'pipeline' });
141+
});
142+
143+
it('renders Calendar view correctly', () => {
144+
mockUseParams.mockReturnValue({ objectName: 'todo_task' });
145+
mockSearchParams.set('view', 'calendar');
146+
147+
render(<ObjectView dataSource={mockDataSource} objects={mockObjects} onEdit={vi.fn()} />);
148+
149+
expect(screen.getByTestId('object-calendar')).toBeInTheDocument();
150+
expect(screen.getByText('Calendar View: due_date')).toBeInTheDocument();
151+
});
152+
});

apps/console/src/components/ObjectView.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ export function ObjectView({ dataSource, objects, onEdit }: any) {
7070
};
7171

7272
const renderCurrentView = () => {
73+
const key = `${objectName}-${activeView.id}-${refreshKey}`;
7374
const commonProps = {
74-
key: `${objectName}-${activeView.id}-${refreshKey}`,
7575
dataSource,
7676
className: "h-full border-none"
7777
};
@@ -86,6 +86,7 @@ export function ObjectView({ dataSource, objects, onEdit }: any) {
8686
case 'kanban':
8787
return (
8888
<ObjectKanban
89+
key={key}
8990
{...commonProps}
9091
schema={{
9192
type: 'kanban',
@@ -101,6 +102,7 @@ export function ObjectView({ dataSource, objects, onEdit }: any) {
101102
case 'calendar':
102103
return (
103104
<ObjectCalendar
105+
key={key}
104106
{...commonProps}
105107
schema={{
106108
type: 'calendar',
@@ -116,6 +118,7 @@ export function ObjectView({ dataSource, objects, onEdit }: any) {
116118
default:
117119
return (
118120
<ObjectGrid
121+
key={key}
119122
{...commonProps}
120123
schema={{
121124
type: 'object-grid',

0 commit comments

Comments
 (0)