@@ -626,3 +626,318 @@ describe('RecordPickerDialog — Keyboard Navigation', () => {
626626 } ) ;
627627 } ) ;
628628} ) ;
629+
630+ // ------------- RecordPickerDialog — lookup_filters consumption -------------
631+
632+ describe ( 'RecordPickerDialog — lookup_filters' , ( ) => {
633+ const basePickerProps = {
634+ open : true ,
635+ onOpenChange : vi . fn ( ) ,
636+ dataSource : mockDataSource as any ,
637+ objectName : 'customers' ,
638+ onSelect : vi . fn ( ) ,
639+ } ;
640+
641+ it ( 'injects lookup_filters into $filter on every query' , async ( ) => {
642+ mockDataSource . find . mockResolvedValue ( {
643+ data : [ { id : '1' , name : 'Active Customer' } ] ,
644+ total : 1 ,
645+ } ) ;
646+
647+ render (
648+ < RecordPickerDialog
649+ { ...basePickerProps }
650+ lookupFilters = { [
651+ { field : 'status' , operator : 'eq' , value : 'active' } ,
652+ ] }
653+ /> ,
654+ ) ;
655+
656+ await waitFor ( ( ) => {
657+ expect ( mockDataSource . find ) . toHaveBeenCalledWith ( 'customers' , {
658+ $top : 10 ,
659+ $skip : 0 ,
660+ $filter : { status : 'active' } ,
661+ } ) ;
662+ } ) ;
663+ } ) ;
664+
665+ it ( 'supports multiple lookup_filters with different operators' , async ( ) => {
666+ mockDataSource . find . mockResolvedValue ( { data : [ ] , total : 0 } ) ;
667+
668+ render (
669+ < RecordPickerDialog
670+ { ...basePickerProps }
671+ lookupFilters = { [
672+ { field : 'status' , operator : 'eq' , value : 'active' } ,
673+ { field : 'category' , operator : 'in' , value : [ 'A' , 'B' ] } ,
674+ { field : 'amount' , operator : 'gte' , value : 100 } ,
675+ ] }
676+ /> ,
677+ ) ;
678+
679+ await waitFor ( ( ) => {
680+ expect ( mockDataSource . find ) . toHaveBeenCalledWith ( 'customers' , {
681+ $top : 10 ,
682+ $skip : 0 ,
683+ $filter : {
684+ status : 'active' ,
685+ category : { $in : [ 'A' , 'B' ] } ,
686+ amount : { $gte : 100 } ,
687+ } ,
688+ } ) ;
689+ } ) ;
690+ } ) ;
691+
692+ it ( 'preserves lookup_filters when search query is added' , async ( ) => {
693+ mockDataSource . find . mockResolvedValue ( { data : [ ] , total : 0 } ) ;
694+
695+ render (
696+ < RecordPickerDialog
697+ { ...basePickerProps }
698+ lookupFilters = { [
699+ { field : 'status' , operator : 'eq' , value : 'active' } ,
700+ ] }
701+ /> ,
702+ ) ;
703+
704+ await waitFor ( ( ) => {
705+ expect ( mockDataSource . find ) . toHaveBeenCalledTimes ( 1 ) ;
706+ } ) ;
707+
708+ // Type in search
709+ await act ( async ( ) => {
710+ fireEvent . change ( screen . getByTestId ( 'record-picker-search' ) , {
711+ target : { value : 'acme' } ,
712+ } ) ;
713+ } ) ;
714+
715+ // Wait for debounce
716+ await waitFor (
717+ ( ) => {
718+ expect ( mockDataSource . find ) . toHaveBeenCalledWith ( 'customers' , {
719+ $top : 10 ,
720+ $skip : 0 ,
721+ $search : 'acme' ,
722+ $filter : { status : 'active' } ,
723+ } ) ;
724+ } ,
725+ { timeout : 500 } ,
726+ ) ;
727+ } ) ;
728+ } ) ;
729+
730+ // ------------- RecordPickerDialog — Cell Type Formatter -------------
731+
732+ describe ( 'RecordPickerDialog — Cell Type Formatter' , ( ) => {
733+ const basePickerProps = {
734+ open : true ,
735+ onOpenChange : vi . fn ( ) ,
736+ dataSource : mockDataSource as any ,
737+ objectName : 'items' ,
738+ onSelect : vi . fn ( ) ,
739+ } ;
740+
741+ it ( 'uses cellRenderer for columns with type defined' , async ( ) => {
742+ const mockCellRenderer = vi . fn ( ) . mockReturnValue (
743+ ( { value } : { value : any } ) => < span data-testid = "custom-rendered" > { `FORMATTED:${ value } ` } </ span > ,
744+ ) ;
745+
746+ mockDataSource . find . mockResolvedValue ( {
747+ data : [ { id : '1' , name : 'Widget' , amount : 99.5 } ] ,
748+ total : 1 ,
749+ } ) ;
750+
751+ render (
752+ < RecordPickerDialog
753+ { ...basePickerProps }
754+ columns = { [
755+ { field : 'name' , label : 'Name' } ,
756+ { field : 'amount' , label : 'Amount' , type : 'currency' } ,
757+ ] }
758+ cellRenderer = { mockCellRenderer }
759+ /> ,
760+ ) ;
761+
762+ await waitFor ( ( ) => {
763+ expect ( screen . getByText ( 'Widget' ) ) . toBeInTheDocument ( ) ;
764+ } ) ;
765+
766+ // cellRenderer should have been called for the 'currency' type
767+ expect ( mockCellRenderer ) . toHaveBeenCalledWith ( 'currency' ) ;
768+
769+ // The formatted cell should be in the document
770+ expect ( screen . getByTestId ( 'custom-rendered' ) ) . toBeInTheDocument ( ) ;
771+ expect ( screen . getByText ( 'FORMATTED:99.5' ) ) . toBeInTheDocument ( ) ;
772+ } ) ;
773+
774+ it ( 'falls back to plain text when no cellRenderer is provided' , async ( ) => {
775+ mockDataSource . find . mockResolvedValue ( {
776+ data : [ { id : '1' , name : 'Widget' , active : true } ] ,
777+ total : 1 ,
778+ } ) ;
779+
780+ render (
781+ < RecordPickerDialog
782+ { ...basePickerProps }
783+ columns = { [
784+ { field : 'name' } ,
785+ { field : 'active' , type : 'boolean' } ,
786+ ] }
787+ /> ,
788+ ) ;
789+
790+ await waitFor ( ( ) => {
791+ expect ( screen . getByText ( 'Widget' ) ) . toBeInTheDocument ( ) ;
792+ // Without cellRenderer, boolean should render as 'Yes'
793+ expect ( screen . getByText ( 'Yes' ) ) . toBeInTheDocument ( ) ;
794+ } ) ;
795+ } ) ;
796+
797+ it ( 'falls back to plain text when column has no type' , async ( ) => {
798+ const mockCellRenderer = vi . fn ( ) ;
799+
800+ mockDataSource . find . mockResolvedValue ( {
801+ data : [ { id : '1' , name : 'Widget' } ] ,
802+ total : 1 ,
803+ } ) ;
804+
805+ render (
806+ < RecordPickerDialog
807+ { ...basePickerProps }
808+ columns = { [ { field : 'name' } ] }
809+ cellRenderer = { mockCellRenderer }
810+ /> ,
811+ ) ;
812+
813+ await waitFor ( ( ) => {
814+ expect ( screen . getByText ( 'Widget' ) ) . toBeInTheDocument ( ) ;
815+ } ) ;
816+
817+ // cellRenderer should NOT be called for columns without type
818+ expect ( mockCellRenderer ) . not . toHaveBeenCalled ( ) ;
819+ } ) ;
820+ } ) ;
821+
822+ // ------------- RecordPickerDialog — FilterUI bar integration -------------
823+
824+ describe ( 'RecordPickerDialog — Filter Bar' , ( ) => {
825+ const basePickerProps = {
826+ open : true ,
827+ onOpenChange : vi . fn ( ) ,
828+ dataSource : mockDataSource as any ,
829+ objectName : 'customers' ,
830+ onSelect : vi . fn ( ) ,
831+ } ;
832+
833+ it ( 'renders filter bar toggle when filterColumns are provided' , async ( ) => {
834+ mockDataSource . find . mockResolvedValue ( { data : [ ] , total : 0 } ) ;
835+
836+ render (
837+ < RecordPickerDialog
838+ { ...basePickerProps }
839+ filterColumns = { [
840+ { field : 'status' , label : 'Status' , type : 'text' } ,
841+ ] }
842+ /> ,
843+ ) ;
844+
845+ await waitFor ( ( ) => {
846+ expect ( screen . getByTestId ( 'record-picker-filter-bar' ) ) . toBeInTheDocument ( ) ;
847+ expect ( screen . getByText ( 'Filters' ) ) . toBeInTheDocument ( ) ;
848+ } ) ;
849+ } ) ;
850+
851+ it ( 'does not render filter bar when no filterColumns' , async ( ) => {
852+ mockDataSource . find . mockResolvedValue ( { data : [ ] , total : 0 } ) ;
853+
854+ render ( < RecordPickerDialog { ...basePickerProps } /> ) ;
855+
856+ await waitFor ( ( ) => {
857+ expect ( screen . queryByTestId ( 'record-picker-filter-bar' ) ) . not . toBeInTheDocument ( ) ;
858+ } ) ;
859+ } ) ;
860+
861+ it ( 'opens filter panel on toggle click' , async ( ) => {
862+ mockDataSource . find . mockResolvedValue ( { data : [ ] , total : 0 } ) ;
863+
864+ render (
865+ < RecordPickerDialog
866+ { ...basePickerProps }
867+ filterColumns = { [
868+ { field : 'name' , label : 'Name' , type : 'text' } ,
869+ ] }
870+ /> ,
871+ ) ;
872+
873+ await waitFor ( ( ) => {
874+ expect ( screen . getByTestId ( 'record-picker-filter-bar' ) ) . toBeInTheDocument ( ) ;
875+ } ) ;
876+
877+ // Filter panel should not be visible yet
878+ expect ( screen . queryByTestId ( 'record-picker-filter-panel' ) ) . not . toBeInTheDocument ( ) ;
879+
880+ // Click Filters button
881+ await act ( async ( ) => {
882+ fireEvent . click ( screen . getByText ( 'Filters' ) ) ;
883+ } ) ;
884+
885+ expect ( screen . getByTestId ( 'record-picker-filter-panel' ) ) . toBeInTheDocument ( ) ;
886+ } ) ;
887+ } ) ;
888+
889+ // ------------- RecordPickerDialog — Column Resize Handles -------------
890+
891+ describe ( 'RecordPickerDialog — Column Resize' , ( ) => {
892+ const basePickerProps = {
893+ open : true ,
894+ onOpenChange : vi . fn ( ) ,
895+ dataSource : mockDataSource as any ,
896+ objectName : 'customers' ,
897+ onSelect : vi . fn ( ) ,
898+ } ;
899+
900+ it ( 'renders resize handles on column headers' , async ( ) => {
901+ mockDataSource . find . mockResolvedValue ( {
902+ data : [ { id : '1' , name : 'Test' , email : 'test@test.com' } ] ,
903+ total : 1 ,
904+ } ) ;
905+
906+ render (
907+ < RecordPickerDialog
908+ { ...basePickerProps }
909+ columns = { [ 'name' , 'email' ] }
910+ /> ,
911+ ) ;
912+
913+ await waitFor ( ( ) => {
914+ expect ( screen . getByText ( 'Test' ) ) . toBeInTheDocument ( ) ;
915+ } ) ;
916+
917+ // Resize handles should be present
918+ expect ( screen . getByTestId ( 'resize-handle-name' ) ) . toBeInTheDocument ( ) ;
919+ expect ( screen . getByTestId ( 'resize-handle-email' ) ) . toBeInTheDocument ( ) ;
920+ } ) ;
921+
922+ it ( 'resize handles have col-resize cursor and separator role' , async ( ) => {
923+ mockDataSource . find . mockResolvedValue ( {
924+ data : [ { id : '1' , name : 'Test' } ] ,
925+ total : 1 ,
926+ } ) ;
927+
928+ render (
929+ < RecordPickerDialog
930+ { ...basePickerProps }
931+ columns = { [ 'name' ] }
932+ /> ,
933+ ) ;
934+
935+ await waitFor ( ( ) => {
936+ expect ( screen . getByText ( 'Test' ) ) . toBeInTheDocument ( ) ;
937+ } ) ;
938+
939+ const handle = screen . getByTestId ( 'resize-handle-name' ) ;
940+ expect ( handle ) . toHaveAttribute ( 'role' , 'separator' ) ;
941+ expect ( handle . className ) . toContain ( 'cursor-col-resize' ) ;
942+ } ) ;
943+ } ) ;
0 commit comments