Skip to content

Commit 19f8c4b

Browse files
committed
feat: enhance ObjectGrid and ListColumn with link and action properties, add tests for custom cell rendering
1 parent 2b6c733 commit 19f8c4b

File tree

8 files changed

+704
-26
lines changed

8 files changed

+704
-26
lines changed

ROADMAP.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,25 @@ Reusable `useNavigationOverlay` hook in @object-ui/react + `NavigationOverlay` c
142142

143143
### 🟡 Medium Priority Gaps
144144

145-
#### 4. ListColumn Extensions
145+
#### 4. ListColumn Extensions
146146

147147
**Spec Requirement:**
148148
```typescript
149149
ListColumn.link: boolean // Primary navigation column
150150
ListColumn.action: string // Associated action ID
151151
```
152152

153-
**Current State:** Not in types, not rendered.
153+
**Current State:** ✅ Complete — types aligned with @objectstack/spec, rendering implemented.
154154

155-
**Tasks:**
156-
- [ ] Add `link` and `action` to ListColumnSchema
157-
- [ ] Render link columns with navigation behavior
158-
- [ ] Render action columns with ActionRunner integration
155+
**Completed:**
156+
- [x] Add `link` and `action` to ListColumn interface + Zod schema
157+
- [x] Add `col.cell()` custom renderer support to data-table
158+
- [x] Render link columns with navigation behavior (triggers useNavigationOverlay)
159+
- [x] Render action columns with ActionRunner integration (dispatches registered handler)
160+
- [x] Support `hidden` columns (filtered out of data-table)
161+
- [x] Support `type` columns (getCellRenderer for typed cell rendering)
162+
- [x] Support `wrap` and `resizable` passthrough
163+
- [x] 28 plugin-grid tests + 4 data-table cell renderer tests, all passing
159164

160165
---
161166

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Data Table - Custom Cell Renderer Tests
3+
*
4+
* Tests that data-table respects col.cell() function for custom cell rendering.
5+
*/
6+
import { describe, it, expect, vi } from 'vitest';
7+
import { render, screen, waitFor } from '@testing-library/react';
8+
import '@testing-library/jest-dom';
9+
import React from 'react';
10+
import { SchemaRenderer } from '@object-ui/react';
11+
12+
// Import data-table to ensure it's registered
13+
import '../data-table';
14+
15+
describe('Data Table: col.cell() custom renderer', () => {
16+
it('should invoke col.cell function to render cell content', async () => {
17+
const cellFn = vi.fn((value: any, _row: any) => (
18+
<span data-testid="custom-cell">{`Custom: ${value}`}</span>
19+
));
20+
21+
const schema = {
22+
type: 'data-table',
23+
columns: [
24+
{ header: 'Name', accessorKey: 'name', cell: cellFn },
25+
{ header: 'Email', accessorKey: 'email' },
26+
],
27+
data: [
28+
{ name: 'Alice', email: 'alice@test.com' },
29+
{ name: 'Bob', email: 'bob@test.com' },
30+
],
31+
pagination: false,
32+
searchable: false,
33+
};
34+
35+
render(<SchemaRenderer schema={schema} />);
36+
37+
await waitFor(() => {
38+
expect(screen.getByText('Custom: Alice')).toBeInTheDocument();
39+
});
40+
41+
expect(screen.getByText('Custom: Bob')).toBeInTheDocument();
42+
expect(cellFn).toHaveBeenCalledTimes(2);
43+
44+
// Non-custom cell should render value directly
45+
expect(screen.getByText('alice@test.com')).toBeInTheDocument();
46+
});
47+
48+
it('should pass both value and row to cell function', async () => {
49+
const cellFn = vi.fn((value: any, row: any) => (
50+
<span data-testid="combo-cell">{`${value} (${row.email})`}</span>
51+
));
52+
53+
const schema = {
54+
type: 'data-table',
55+
columns: [
56+
{ header: 'Name', accessorKey: 'name', cell: cellFn },
57+
],
58+
data: [
59+
{ name: 'Alice', email: 'alice@test.com' },
60+
],
61+
pagination: false,
62+
searchable: false,
63+
};
64+
65+
render(<SchemaRenderer schema={schema} />);
66+
67+
await waitFor(() => {
68+
expect(screen.getByText('Alice (alice@test.com)')).toBeInTheDocument();
69+
});
70+
71+
expect(cellFn).toHaveBeenCalledWith('Alice', expect.objectContaining({ name: 'Alice', email: 'alice@test.com' }));
72+
});
73+
74+
it('should render raw value when no cell function is provided', async () => {
75+
const schema = {
76+
type: 'data-table',
77+
columns: [
78+
{ header: 'Name', accessorKey: 'name' },
79+
],
80+
data: [
81+
{ name: 'Alice' },
82+
],
83+
pagination: false,
84+
searchable: false,
85+
};
86+
87+
render(<SchemaRenderer schema={schema} />);
88+
89+
await waitFor(() => {
90+
expect(screen.getByText('Alice')).toBeInTheDocument();
91+
});
92+
});
93+
94+
it('should prefer editing state over cell function when in edit mode', async () => {
95+
// The editing state should take precedence over custom cell renderer
96+
// This test verifies the rendering priority: editing > cell > raw value
97+
const cellFn = vi.fn((value: any) => (
98+
<span>{`Custom: ${value}`}</span>
99+
));
100+
101+
const schema = {
102+
type: 'data-table',
103+
columns: [
104+
{ header: 'Name', accessorKey: 'name', cell: cellFn },
105+
],
106+
data: [
107+
{ name: 'Alice' },
108+
],
109+
pagination: false,
110+
searchable: false,
111+
editable: false,
112+
};
113+
114+
render(<SchemaRenderer schema={schema} />);
115+
116+
await waitFor(() => {
117+
expect(screen.getByText('Custom: Alice')).toBeInTheDocument();
118+
});
119+
});
120+
});

packages/components/src/renderers/complex/data-table.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,8 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
719719
onKeyDown={handleEditKeyDown}
720720
className="h-8 px-2 py-1"
721721
/>
722+
) : typeof col.cell === 'function' ? (
723+
col.cell(cellValue, row)
722724
) : (
723725
cellValue
724726
)}

0 commit comments

Comments
 (0)