@@ -630,4 +630,265 @@ describe('Combobox2', () => {
630630 . should ( 'have.text' , item . name ) ;
631631 } ) ;
632632 } ) ;
633+
634+ describe ( 'onSearch prop for server-side search' , ( ) => {
635+ it ( 'does not call onSearch on mount' , ( ) => {
636+ const onSearchSpy = cy . spy ( ) . as ( 'onSearchSpy' ) ;
637+
638+ cy . mount (
639+ < Combobox2
640+ values = { items }
641+ value = ""
642+ onSearch = { onSearchSpy }
643+ showSearch
644+ /> ,
645+ ) ;
646+
647+ cy . get ( '@onSearchSpy' ) . should ( 'not.have.been.called' ) ;
648+ } ) ;
649+
650+ it ( 'calls onSearch when user types in tag mode' , ( ) => {
651+ const onSearchSpy = cy . spy ( ) . as ( 'onSearchSpy' ) ;
652+
653+ cy . mount (
654+ < Combobox2
655+ values = { items }
656+ value = { [ ] }
657+ tags
658+ onSearch = { onSearchSpy }
659+ /> ,
660+ ) ;
661+
662+ cy . get ( `.${ classes . entryField } ` )
663+ . type ( 'test' ) ;
664+
665+ cy . wait ( 350 ) ;
666+
667+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledOnce' ) ;
668+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledWith' , 'test' ) ;
669+ } ) ;
670+
671+ it ( 'calls onSearch when user types with showSearch enabled' , ( ) => {
672+ const onSearchSpy = cy . spy ( ) . as ( 'onSearchSpy' ) ;
673+
674+ cy . mount (
675+ < Combobox2
676+ values = { items }
677+ value = ""
678+ onSearch = { onSearchSpy }
679+ showSearch
680+ /> ,
681+ ) ;
682+
683+ cy . get ( `.${ classes . trigger } ` )
684+ . click ( ) ;
685+
686+ cy . get ( `.${ classes . searchField } input` )
687+ . type ( 'search' ) ;
688+
689+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledOnce' ) ;
690+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledWith' , 'search' ) ;
691+ } ) ;
692+
693+ it ( 'debounces onSearch calls correctly' , ( ) => {
694+ const onSearchSpy = cy . spy ( ) . as ( 'onSearchSpy' ) ;
695+
696+ cy . mount (
697+ < Combobox2
698+ values = { items }
699+ value = { [ ] }
700+ tags
701+ onSearch = { onSearchSpy }
702+ /> ,
703+ ) ;
704+
705+ cy . get ( `.${ classes . entryField } ` )
706+ . type ( 'a' ) ;
707+
708+ cy . wait ( 100 ) ;
709+
710+ cy . get ( `.${ classes . entryField } ` )
711+ . type ( 'b' ) ;
712+
713+ cy . wait ( 100 ) ;
714+
715+ cy . get ( `.${ classes . entryField } ` )
716+ . type ( 'c' ) ;
717+
718+ cy . get ( '@onSearchSpy' ) . should ( 'not.have.been.called' ) ;
719+
720+ cy . wait ( 350 ) ;
721+
722+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledOnce' ) ;
723+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledWith' , 'abc' ) ;
724+ } ) ;
725+
726+ it ( 'calls onSearch with empty string when search is cleared after searching' , ( ) => {
727+ const onSearchSpy = cy . spy ( ) . as ( 'onSearchSpy' ) ;
728+
729+ cy . mount (
730+ < Combobox2
731+ values = { items }
732+ value = { [ ] }
733+ tags
734+ onSearch = { onSearchSpy }
735+ /> ,
736+ ) ;
737+
738+ cy . get ( `.${ classes . entryField } ` )
739+ . type ( 'test' ) ;
740+
741+ cy . wait ( 350 ) ;
742+
743+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledWith' , 'test' ) ;
744+
745+ cy . get ( `.${ classes . entryField } ` )
746+ . clear ( ) ;
747+
748+ cy . wait ( 350 ) ;
749+
750+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledWith' , '' ) ;
751+ } ) ;
752+
753+ it ( 'does not perform client-side filtering when onSearch is provided' , ( ) => {
754+ function Comp ( ) {
755+ const [ searchResults , setSearchResults ] = useState ( items ) ;
756+
757+ const handleSearch = ( searchTerm ) => {
758+ if ( ! searchTerm ) {
759+ setSearchResults ( items ) ;
760+ } else {
761+ setSearchResults ( items . filter ( ( item ) => item . name === searchTerm ) ) ;
762+ }
763+ } ;
764+
765+ return (
766+ < Combobox2
767+ values = { searchResults }
768+ value = { [ ] }
769+ tags
770+ onSearch = { handleSearch }
771+ />
772+ ) ;
773+ }
774+
775+ cy . mount ( < Comp /> ) ;
776+
777+ cy . get ( `.${ classes . entryField } ` )
778+ . type ( 'Item2' ) ;
779+
780+ cy . wait ( 350 ) ;
781+
782+ cy . get ( `.${ classes . dropdownList } li` )
783+ . should ( 'have.length' , 1 )
784+ . and ( 'contain.text' , 'Item2' ) ;
785+ } ) ;
786+
787+ it ( 'works correctly with multiple value updates' , ( ) => {
788+ function Comp ( ) {
789+ const [ searchResults , setSearchResults ] = useState ( items ) ;
790+
791+ const handleSearch = ( searchTerm ) => {
792+ if ( ! searchTerm ) {
793+ setSearchResults ( items ) ;
794+ } else {
795+ setSearchResults (
796+ items . filter ( ( item ) => item . name . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ) ,
797+ ) ;
798+ }
799+ } ;
800+
801+ return (
802+ < Combobox2
803+ values = { searchResults }
804+ value = ""
805+ onSearch = { handleSearch }
806+ showSearch
807+ />
808+ ) ;
809+ }
810+
811+ cy . mount ( < Comp /> ) ;
812+
813+ cy . get ( `.${ classes . trigger } ` )
814+ . click ( ) ;
815+
816+ cy . get ( `.${ classes . dropdownList } li` )
817+ . should ( 'have.length' , items . length ) ;
818+
819+ cy . get ( `.${ classes . searchField } input` )
820+ . type ( 'item2' ) ;
821+
822+ cy . wait ( 350 ) ;
823+
824+ cy . get ( `.${ classes . dropdownList } li` )
825+ . should ( 'have.length' , 1 )
826+ . and ( 'contain.text' , 'Item2' ) ;
827+
828+ cy . get ( `.${ classes . searchField } input` )
829+ . clear ( ) ;
830+
831+ cy . wait ( 350 ) ;
832+
833+ cy . get ( `.${ classes . dropdownList } li` )
834+ . should ( 'have.length' , items . length ) ;
835+ } ) ;
836+
837+ it ( 'maintains backward compatibility - works without onSearch prop' , ( ) => {
838+ cy . mount (
839+ < Combobox2
840+ values = { items }
841+ value = ""
842+ showSearch
843+ /> ,
844+ ) ;
845+
846+ cy . get ( `.${ classes . trigger } ` )
847+ . click ( ) ;
848+
849+ cy . get ( `.${ classes . dropdownList } li` )
850+ . should ( 'have.length' , items . length ) ;
851+
852+ cy . get ( `.${ classes . searchField } input` )
853+ . type ( 'Item2' ) ;
854+
855+ cy . get ( `.${ classes . dropdownList } li` )
856+ . should ( 'have.length' , 1 )
857+ . and ( 'contain.text' , 'Item2' ) ;
858+ } ) ;
859+
860+ it ( 'handles rapid typing and clearing correctly' , ( ) => {
861+ const onSearchSpy = cy . spy ( ) . as ( 'onSearchSpy' ) ;
862+
863+ cy . mount (
864+ < Combobox2
865+ values = { items }
866+ value = { [ ] }
867+ tags
868+ onSearch = { onSearchSpy }
869+ /> ,
870+ ) ;
871+
872+ cy . get ( `.${ classes . entryField } ` )
873+ . type ( 'test' ) ;
874+
875+ cy . wait ( 350 ) ;
876+
877+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledWith' , 'test' ) ;
878+
879+ cy . get ( `.${ classes . entryField } ` )
880+ . clear ( ) ;
881+
882+ cy . wait ( 350 ) ;
883+
884+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledWith' , '' ) ;
885+
886+ cy . get ( `.${ classes . entryField } ` )
887+ . type ( 'new' ) ;
888+
889+ cy . wait ( 350 ) ;
890+
891+ cy . get ( '@onSearchSpy' ) . should ( 'have.been.calledWith' , 'new' ) ;
892+ } ) ;
893+ } ) ;
633894} ) ;
0 commit comments