Skip to content

Commit d8e08bc

Browse files
Copilothotlong
andcommitted
feat(fields): add renderFilterBar/renderGrid slots, auto-generate filters from lookupFilters
- Add renderFilterBar slot prop for FilterUI injection from plugin-view - Add renderGrid slot prop for ObjectGrid injection from plugin-grid - Auto-generate filterColumns from lookupFilters when no explicit ones given - Export RecordPickerFilterBarProps and RecordPickerGridSlotProps interfaces - Add 6 new tests for slot props and auto-generation - Total: 37 record-picker tests, 412 fields tests passing Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent a08a104 commit d8e08bc

2 files changed

Lines changed: 534 additions & 167 deletions

File tree

packages/fields/src/record-picker.test.tsx

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,3 +941,207 @@ describe('RecordPickerDialog — Column Resize', () => {
941941
expect(handle.className).toContain('cursor-col-resize');
942942
});
943943
});
944+
945+
// ------------- RecordPickerDialog — renderFilterBar slot (FilterUI integration) ---
946+
947+
describe('RecordPickerDialog — renderFilterBar slot', () => {
948+
const basePickerProps = {
949+
open: true,
950+
onOpenChange: vi.fn(),
951+
dataSource: mockDataSource as any,
952+
objectName: 'customers',
953+
onSelect: vi.fn(),
954+
};
955+
956+
it('calls renderFilterBar with correct props when provided', async () => {
957+
mockDataSource.find.mockResolvedValue({ data: [], total: 0 });
958+
959+
const renderFilterBar = vi.fn().mockReturnValue(
960+
<div data-testid="custom-filter-bar">Custom FilterUI</div>,
961+
);
962+
963+
render(
964+
<RecordPickerDialog
965+
{...basePickerProps}
966+
filterColumns={[
967+
{ field: 'status', label: 'Status', type: 'select', options: [{ label: 'Active', value: 'active' }] },
968+
]}
969+
renderFilterBar={renderFilterBar}
970+
/>,
971+
);
972+
973+
await waitFor(() => {
974+
expect(screen.getByTestId('custom-filter-bar')).toBeInTheDocument();
975+
});
976+
977+
// renderFilterBar should have been called with FilterBarProps
978+
expect(renderFilterBar).toHaveBeenCalledWith(
979+
expect.objectContaining({
980+
filterColumns: expect.arrayContaining([
981+
expect.objectContaining({ field: 'status', type: 'select' }),
982+
]),
983+
values: {},
984+
onChange: expect.any(Function),
985+
onClear: expect.any(Function),
986+
activeCount: 0,
987+
}),
988+
);
989+
});
990+
991+
it('hides built-in filter bar when renderFilterBar is provided', async () => {
992+
mockDataSource.find.mockResolvedValue({ data: [], total: 0 });
993+
994+
render(
995+
<RecordPickerDialog
996+
{...basePickerProps}
997+
filterColumns={[{ field: 'name', label: 'Name', type: 'text' }]}
998+
renderFilterBar={() => <div data-testid="external-filter">External</div>}
999+
/>,
1000+
);
1001+
1002+
await waitFor(() => {
1003+
expect(screen.getByTestId('external-filter')).toBeInTheDocument();
1004+
});
1005+
1006+
// Built-in filter panel toggle button should NOT be present
1007+
expect(screen.queryByTestId('record-picker-filter-panel')).not.toBeInTheDocument();
1008+
});
1009+
});
1010+
1011+
// ------------- RecordPickerDialog — renderGrid slot (ObjectGrid reuse) ---
1012+
1013+
describe('RecordPickerDialog — renderGrid slot', () => {
1014+
const basePickerProps = {
1015+
open: true,
1016+
onOpenChange: vi.fn(),
1017+
dataSource: mockDataSource as any,
1018+
objectName: 'customers',
1019+
onSelect: vi.fn(),
1020+
};
1021+
1022+
it('renders external grid component via renderGrid slot', async () => {
1023+
mockDataSource.find.mockResolvedValue({
1024+
data: [{ id: '1', name: 'Acme Corp' }],
1025+
total: 1,
1026+
});
1027+
1028+
const renderGrid = vi.fn().mockReturnValue(
1029+
<div data-testid="custom-grid">Custom ObjectGrid</div>,
1030+
);
1031+
1032+
render(
1033+
<RecordPickerDialog
1034+
{...basePickerProps}
1035+
columns={['name']}
1036+
renderGrid={renderGrid}
1037+
/>,
1038+
);
1039+
1040+
await waitFor(() => {
1041+
expect(screen.getByTestId('record-picker-grid-slot')).toBeInTheDocument();
1042+
expect(screen.getByTestId('custom-grid')).toBeInTheDocument();
1043+
});
1044+
1045+
// renderGrid should have been called with grid slot props
1046+
expect(renderGrid).toHaveBeenCalledWith(
1047+
expect.objectContaining({
1048+
columns: expect.arrayContaining([
1049+
expect.objectContaining({ field: 'name' }),
1050+
]),
1051+
records: expect.arrayContaining([
1052+
expect.objectContaining({ id: '1', name: 'Acme Corp' }),
1053+
]),
1054+
loading: false,
1055+
totalCount: 1,
1056+
currentPage: 1,
1057+
pageSize: 10,
1058+
sortField: null,
1059+
sortDirection: 'asc',
1060+
onSort: expect.any(Function),
1061+
onPageChange: expect.any(Function),
1062+
onRowClick: expect.any(Function),
1063+
isSelected: expect.any(Function),
1064+
multiple: false,
1065+
idField: 'id',
1066+
}),
1067+
);
1068+
});
1069+
1070+
it('hides built-in table when renderGrid is provided', async () => {
1071+
mockDataSource.find.mockResolvedValue({
1072+
data: [{ id: '1', name: 'Acme Corp' }],
1073+
total: 1,
1074+
});
1075+
1076+
render(
1077+
<RecordPickerDialog
1078+
{...basePickerProps}
1079+
columns={['name']}
1080+
renderGrid={() => <div>Custom Grid</div>}
1081+
/>,
1082+
);
1083+
1084+
await waitFor(() => {
1085+
expect(screen.getByText('Custom Grid')).toBeInTheDocument();
1086+
});
1087+
1088+
// Built-in table should NOT be present
1089+
expect(screen.queryByRole('grid')).not.toBeInTheDocument();
1090+
// Built-in pagination should NOT be present
1091+
expect(screen.queryByTestId('record-picker-pagination')).not.toBeInTheDocument();
1092+
});
1093+
});
1094+
1095+
// ------------- RecordPickerDialog — Auto-generated filterColumns from lookupFilters ---
1096+
1097+
describe('RecordPickerDialog — Auto-generated filter bar from lookupFilters', () => {
1098+
const basePickerProps = {
1099+
open: true,
1100+
onOpenChange: vi.fn(),
1101+
dataSource: mockDataSource as any,
1102+
objectName: 'customers',
1103+
onSelect: vi.fn(),
1104+
};
1105+
1106+
it('auto-generates filter bar from lookupFilters when no filterColumns given', async () => {
1107+
mockDataSource.find.mockResolvedValue({ data: [], total: 0 });
1108+
1109+
render(
1110+
<RecordPickerDialog
1111+
{...basePickerProps}
1112+
lookupFilters={[
1113+
{ field: 'status', operator: 'eq', value: 'active' },
1114+
{ field: 'amount', operator: 'gte', value: 100 },
1115+
]}
1116+
/>,
1117+
);
1118+
1119+
await waitFor(() => {
1120+
// Filter bar should appear because lookupFilters auto-generate filterColumns
1121+
expect(screen.getByTestId('record-picker-filter-bar')).toBeInTheDocument();
1122+
});
1123+
});
1124+
1125+
it('prefers explicit filterColumns over auto-generated ones', async () => {
1126+
mockDataSource.find.mockResolvedValue({ data: [], total: 0 });
1127+
1128+
const renderFilterBar = vi.fn().mockReturnValue(<div>Filters</div>);
1129+
1130+
render(
1131+
<RecordPickerDialog
1132+
{...basePickerProps}
1133+
lookupFilters={[{ field: 'status', operator: 'eq', value: 'active' }]}
1134+
filterColumns={[{ field: 'custom_field', label: 'Custom', type: 'text' }]}
1135+
renderFilterBar={renderFilterBar}
1136+
/>,
1137+
);
1138+
1139+
await waitFor(() => {
1140+
expect(renderFilterBar).toHaveBeenCalled();
1141+
});
1142+
1143+
// Should use the explicit filterColumns, not auto-generated ones
1144+
const calledProps = renderFilterBar.mock.calls[0][0];
1145+
expect(calledProps.filterColumns[0].field).toBe('custom_field');
1146+
});
1147+
});

0 commit comments

Comments
 (0)