@object-ui/plugin-view 3.1.0 → 3.1.2
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.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +23 -0
- package/dist/index.js +1093 -1061
- package/dist/index.umd.cjs +3 -3
- package/package.json +7 -7
- package/src/FilterUI.tsx +33 -0
- package/src/ObjectView.tsx +11 -3
- package/src/__tests__/FilterUI.test.tsx +97 -0
- package/src/__tests__/ObjectView.test.tsx +40 -0
- package/src/__tests__/registration.test.tsx +0 -32
package/src/ObjectView.tsx
CHANGED
|
@@ -419,7 +419,7 @@ export const ObjectView: React.FC<ObjectViewProps> = ({
|
|
|
419
419
|
}
|
|
420
420
|
if (navigationConfig.mode === 'new_window' || navigationConfig.openNewTab) {
|
|
421
421
|
const recordId = record._id || record.id;
|
|
422
|
-
const url = `/${schema.objectName}/${recordId}`;
|
|
422
|
+
const url = `/${schema.objectName}/${encodeURIComponent(String(recordId))}`;
|
|
423
423
|
window.open(url, '_blank');
|
|
424
424
|
return;
|
|
425
425
|
}
|
|
@@ -546,7 +546,7 @@ export const ObjectView: React.FC<ObjectViewProps> = ({
|
|
|
546
546
|
|
|
547
547
|
const filterableFieldDefs = fieldEntries.map(([key, f]: [string, any]) => {
|
|
548
548
|
const fieldType = f.type || 'text';
|
|
549
|
-
let filterType: 'text' | 'number' | 'select' | 'date' | 'boolean' = 'text';
|
|
549
|
+
let filterType: 'text' | 'number' | 'select' | 'multi-select' | 'date' | 'boolean' = 'text';
|
|
550
550
|
let options: Array<{ label: string; value: any }> | undefined;
|
|
551
551
|
|
|
552
552
|
if (fieldType === 'number' || fieldType === 'currency' || fieldType === 'percent') {
|
|
@@ -555,11 +555,18 @@ export const ObjectView: React.FC<ObjectViewProps> = ({
|
|
|
555
555
|
filterType = 'boolean';
|
|
556
556
|
} else if (fieldType === 'date' || fieldType === 'datetime') {
|
|
557
557
|
filterType = 'date';
|
|
558
|
-
} else if (fieldType === 'select' || f.options) {
|
|
558
|
+
} else if (fieldType === 'select' || fieldType === 'status' || f.options) {
|
|
559
559
|
filterType = 'select';
|
|
560
560
|
options = (f.options || []).map((o: any) =>
|
|
561
561
|
typeof o === 'string' ? { label: o, value: o } : { label: o.label, value: o.value },
|
|
562
562
|
);
|
|
563
|
+
} else if (fieldType === 'lookup' || fieldType === 'master_detail' || fieldType === 'user' || fieldType === 'owner') {
|
|
564
|
+
if (f.options && f.options.length > 0) {
|
|
565
|
+
filterType = 'multi-select';
|
|
566
|
+
options = (f.options || []).map((o: any) =>
|
|
567
|
+
typeof o === 'string' ? { label: o, value: o } : { label: o.label, value: o.value },
|
|
568
|
+
);
|
|
569
|
+
}
|
|
563
570
|
}
|
|
564
571
|
return {
|
|
565
572
|
field: key,
|
|
@@ -875,6 +882,7 @@ export const ObjectView: React.FC<ObjectViewProps> = ({
|
|
|
875
882
|
addRecord: activeView?.addRecord ?? (schema as any).addRecord,
|
|
876
883
|
conditionalFormatting: activeView?.conditionalFormatting ?? (schema as any).conditionalFormatting,
|
|
877
884
|
quickFilters: activeView?.quickFilters ?? (schema as any).quickFilters,
|
|
885
|
+
userFilters: activeView?.userFilters ?? (schema as any).userFilters,
|
|
878
886
|
showRecordCount: activeView?.showRecordCount ?? (schema as any).showRecordCount,
|
|
879
887
|
allowPrinting: activeView?.allowPrinting ?? (schema as any).allowPrinting,
|
|
880
888
|
virtualScroll: activeView?.virtualScroll ?? (schema as any).virtualScroll,
|
|
@@ -541,4 +541,101 @@ describe('FilterUI', () => {
|
|
|
541
541
|
window.removeEventListener('filter:changed', spy);
|
|
542
542
|
});
|
|
543
543
|
});
|
|
544
|
+
|
|
545
|
+
// -------------------------------------------------------------------------
|
|
546
|
+
// 9. Multi-select filter type
|
|
547
|
+
// -------------------------------------------------------------------------
|
|
548
|
+
describe('multi-select filter type', () => {
|
|
549
|
+
const multiSelectFilters: FilterUISchema['filters'] = [
|
|
550
|
+
{
|
|
551
|
+
field: 'tags',
|
|
552
|
+
label: 'Tags',
|
|
553
|
+
type: 'multi-select',
|
|
554
|
+
options: [
|
|
555
|
+
{ label: 'Frontend', value: 'frontend' },
|
|
556
|
+
{ label: 'Backend', value: 'backend' },
|
|
557
|
+
{ label: 'DevOps', value: 'devops' },
|
|
558
|
+
],
|
|
559
|
+
},
|
|
560
|
+
];
|
|
561
|
+
|
|
562
|
+
it('renders checkboxes for multi-select type', () => {
|
|
563
|
+
render(
|
|
564
|
+
<FilterUI
|
|
565
|
+
schema={makeSchema({ filters: multiSelectFilters })}
|
|
566
|
+
/>,
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
expect(screen.getByText('Tags')).toBeInTheDocument();
|
|
570
|
+
const checkboxes = screen.getAllByTestId('checkbox');
|
|
571
|
+
expect(checkboxes.length).toBe(3);
|
|
572
|
+
expect(screen.getByText('Frontend')).toBeInTheDocument();
|
|
573
|
+
expect(screen.getByText('Backend')).toBeInTheDocument();
|
|
574
|
+
expect(screen.getByText('DevOps')).toBeInTheDocument();
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('calls onChange with array when multi-select checkbox is toggled', () => {
|
|
578
|
+
const onChange = vi.fn();
|
|
579
|
+
render(
|
|
580
|
+
<FilterUI
|
|
581
|
+
schema={makeSchema({ filters: multiSelectFilters })}
|
|
582
|
+
onChange={onChange}
|
|
583
|
+
/>,
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
const checkboxes = screen.getAllByTestId('checkbox');
|
|
587
|
+
fireEvent.click(checkboxes[0]); // Frontend
|
|
588
|
+
expect(onChange).toHaveBeenCalledWith({ tags: ['frontend'] });
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('adds to selection when another checkbox is checked', () => {
|
|
592
|
+
const onChange = vi.fn();
|
|
593
|
+
render(
|
|
594
|
+
<FilterUI
|
|
595
|
+
schema={makeSchema({
|
|
596
|
+
filters: multiSelectFilters,
|
|
597
|
+
values: { tags: ['frontend'] },
|
|
598
|
+
})}
|
|
599
|
+
onChange={onChange}
|
|
600
|
+
/>,
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
const checkboxes = screen.getAllByTestId('checkbox');
|
|
604
|
+
fireEvent.click(checkboxes[1]); // Backend
|
|
605
|
+
expect(onChange).toHaveBeenCalledWith({ tags: ['frontend', 'backend'] });
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('removes from selection when checkbox is unchecked', () => {
|
|
609
|
+
const onChange = vi.fn();
|
|
610
|
+
render(
|
|
611
|
+
<FilterUI
|
|
612
|
+
schema={makeSchema({
|
|
613
|
+
filters: multiSelectFilters,
|
|
614
|
+
values: { tags: ['frontend', 'backend'] },
|
|
615
|
+
})}
|
|
616
|
+
onChange={onChange}
|
|
617
|
+
/>,
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
// Uncheck Frontend (first checkbox)
|
|
621
|
+
const checkboxes = screen.getAllByTestId('checkbox');
|
|
622
|
+
fireEvent.click(checkboxes[0]); // Frontend
|
|
623
|
+
expect(onChange).toHaveBeenCalledWith({ tags: ['backend'] });
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('shows selected count in active badge for popover layout', () => {
|
|
627
|
+
render(
|
|
628
|
+
<FilterUI
|
|
629
|
+
schema={makeSchema({
|
|
630
|
+
layout: 'popover',
|
|
631
|
+
filters: multiSelectFilters,
|
|
632
|
+
values: { tags: ['frontend', 'backend'] },
|
|
633
|
+
})}
|
|
634
|
+
/>,
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
// Active count should be 1 (tags field has a value)
|
|
638
|
+
expect(screen.getByText('1')).toBeInTheDocument();
|
|
639
|
+
});
|
|
640
|
+
});
|
|
544
641
|
});
|
|
@@ -661,5 +661,45 @@ describe('ObjectView', () => {
|
|
|
661
661
|
expect(callSchema?.showFilters).toBe(false);
|
|
662
662
|
expect(callSchema?.showSort).toBe(false);
|
|
663
663
|
});
|
|
664
|
+
|
|
665
|
+
it('should propagate userFilters from activeView in renderListView', async () => {
|
|
666
|
+
const schema: ObjectViewSchema = {
|
|
667
|
+
type: 'object-view',
|
|
668
|
+
objectName: 'contacts',
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const renderListViewSpy = vi.fn(({ schema: listSchema }: any) => (
|
|
672
|
+
<div data-testid="custom-list">Custom ListView</div>
|
|
673
|
+
));
|
|
674
|
+
|
|
675
|
+
const views = [
|
|
676
|
+
{
|
|
677
|
+
id: 'v1',
|
|
678
|
+
label: 'View 1',
|
|
679
|
+
type: 'grid' as const,
|
|
680
|
+
userFilters: {
|
|
681
|
+
element: 'dropdown' as const,
|
|
682
|
+
fields: [{ field: 'status' }],
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
];
|
|
686
|
+
|
|
687
|
+
render(
|
|
688
|
+
<ObjectView
|
|
689
|
+
schema={schema}
|
|
690
|
+
dataSource={mockDataSource}
|
|
691
|
+
views={views}
|
|
692
|
+
activeViewId="v1"
|
|
693
|
+
renderListView={renderListViewSpy}
|
|
694
|
+
/>,
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
expect(renderListViewSpy).toHaveBeenCalled();
|
|
698
|
+
const callSchema = renderListViewSpy.mock.calls[0]?.[0]?.schema;
|
|
699
|
+
expect(callSchema?.userFilters).toEqual({
|
|
700
|
+
element: 'dropdown',
|
|
701
|
+
fields: [{ field: 'status' }],
|
|
702
|
+
});
|
|
703
|
+
});
|
|
664
704
|
});
|
|
665
705
|
});
|
|
@@ -1,32 +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
|
-
import { describe, it, expect } from 'vitest';
|
|
10
|
-
import { ObjectView, ViewSwitcher, FilterUI, SortUI } from '../index';
|
|
11
|
-
|
|
12
|
-
describe('Plugin View Registration', () => {
|
|
13
|
-
it('exports ObjectView component', () => {
|
|
14
|
-
expect(ObjectView).toBeDefined();
|
|
15
|
-
expect(typeof ObjectView).toBe('function');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('exports ViewSwitcher component', () => {
|
|
19
|
-
expect(ViewSwitcher).toBeDefined();
|
|
20
|
-
expect(typeof ViewSwitcher).toBe('function');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('exports FilterUI component', () => {
|
|
24
|
-
expect(FilterUI).toBeDefined();
|
|
25
|
-
expect(typeof FilterUI).toBe('function');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('exports SortUI component', () => {
|
|
29
|
-
expect(SortUI).toBeDefined();
|
|
30
|
-
expect(typeof SortUI).toBe('function');
|
|
31
|
-
});
|
|
32
|
-
});
|