@@ -629,6 +629,216 @@ describe('MentionsInput', () => {
629629 } )
630630 } )
631631
632+ describe ( 'autoResize' , ( ) => {
633+ it ( 'resizes the textarea to the scroll height after a controlled value update' , ( ) => {
634+ const onMentionsChange = jest . fn ( )
635+ const { rerender } = render (
636+ < MentionsInput autoResize value = "short" onMentionsChange = { onMentionsChange } >
637+ < Mention trigger = "@" data = { data } />
638+ </ MentionsInput >
639+ )
640+
641+ const combobox = screen . getByRole ( 'combobox' )
642+ let scrollHeight = 0
643+ Object . defineProperty ( combobox , 'scrollHeight' , {
644+ configurable : true ,
645+ get : ( ) => scrollHeight ,
646+ } )
647+
648+ expect ( combobox . style . height ) . not . toBe ( '64px' )
649+
650+ scrollHeight = 64
651+
652+ rerender (
653+ < MentionsInput
654+ autoResize
655+ value = "a slightly longer value"
656+ onMentionsChange = { onMentionsChange }
657+ >
658+ < Mention trigger = "@" data = { data } />
659+ </ MentionsInput >
660+ )
661+
662+ const computed = window . getComputedStyle ( combobox )
663+ const borderTop = Number . parseFloat ( computed . borderTopWidth || '0' )
664+ const borderBottom = Number . parseFloat ( computed . borderBottomWidth || '0' )
665+ expect ( Number . parseFloat ( combobox . style . height ) ) . toBe ( scrollHeight + borderTop + borderBottom )
666+ expect ( combobox . style . overflowY ) . toBe ( 'hidden' )
667+ } )
668+
669+ it ( 'updates the textarea height when autoResize toggles from false to true' , ( ) => {
670+ const onMentionsChange = jest . fn ( )
671+ const { rerender } = render (
672+ < MentionsInput autoResize = { false } value = "initial" onMentionsChange = { onMentionsChange } >
673+ < Mention trigger = "@" data = { data } />
674+ </ MentionsInput >
675+ )
676+
677+ const combobox = screen . getByRole ( 'combobox' )
678+ let scrollHeight = 0
679+ Object . defineProperty ( combobox , 'scrollHeight' , {
680+ configurable : true ,
681+ get : ( ) => scrollHeight ,
682+ } )
683+
684+ expect ( combobox . style . height ) . toBe ( '' )
685+
686+ scrollHeight = 88
687+
688+ rerender (
689+ < MentionsInput autoResize value = "initial" onMentionsChange = { onMentionsChange } >
690+ < Mention trigger = "@" data = { data } />
691+ </ MentionsInput >
692+ )
693+
694+ const computed = window . getComputedStyle ( combobox )
695+ const borderTop = Number . parseFloat ( computed . borderTopWidth || '0' )
696+ const borderBottom = Number . parseFloat ( computed . borderBottomWidth || '0' )
697+ expect ( Number . parseFloat ( combobox . style . height ) ) . toBe ( scrollHeight + borderTop + borderBottom )
698+ expect ( combobox . style . overflowY ) . toBe ( 'hidden' )
699+ } )
700+
701+ it ( 'applies the latest scroll height after user input changes while autoResize is enabled' , async ( ) => {
702+ const ControlledInput = ( ) => {
703+ const [ value , setValue ] = React . useState ( 'short text' )
704+ return (
705+ < MentionsInput
706+ autoResize
707+ value = { value }
708+ onMentionsChange = { ( { value : nextValue } ) => setValue ( nextValue ) }
709+ >
710+ < Mention trigger = "@" data = { data } />
711+ </ MentionsInput >
712+ )
713+ }
714+
715+ render ( < ControlledInput /> )
716+
717+ const combobox = screen . getByRole ( 'combobox' )
718+ let scrollHeight = 0
719+ Object . defineProperty ( combobox , 'scrollHeight' , {
720+ configurable : true ,
721+ get : ( ) => scrollHeight ,
722+ } )
723+
724+ const updatedValue = `${ combobox . value } that is now longer`
725+ combobox . focus ( )
726+ combobox . setSelectionRange ( combobox . value . length , combobox . value . length )
727+
728+ scrollHeight = 150
729+
730+ await act ( async ( ) => {
731+ fireEvent . change ( combobox , {
732+ target : {
733+ value : updatedValue ,
734+ selectionStart : updatedValue . length ,
735+ selectionEnd : updatedValue . length ,
736+ } ,
737+ } )
738+ } )
739+
740+ await waitFor ( ( ) => {
741+ const computed = window . getComputedStyle ( combobox )
742+ const borderTop = Number . parseFloat ( computed . borderTopWidth || '0' )
743+ const borderBottom = Number . parseFloat ( computed . borderBottomWidth || '0' )
744+ expect ( Number . parseFloat ( combobox . style . height ) ) . toBe (
745+ scrollHeight + borderTop + borderBottom
746+ )
747+ expect ( combobox . style . overflowY ) . toBe ( 'hidden' )
748+ } )
749+ } )
750+
751+ it ( 'skips resizing when rendering a single-line input' , ( ) => {
752+ render (
753+ < MentionsInput autoResize singleLine value = "single line" >
754+ < Mention trigger = "@" data = { data } />
755+ </ MentionsInput >
756+ )
757+
758+ const input = screen . getByRole ( 'combobox' )
759+ expect ( input . tagName ) . toBe ( 'INPUT' )
760+ expect ( input . style . height ) . toBe ( '' )
761+ expect ( input . style . overflowY ) . toBe ( '' )
762+ } )
763+
764+ it ( 'adds border widths to the measured height' , ( ) => {
765+ const onMentionsChange = jest . fn ( )
766+ const getComputedStyleSpy = jest . spyOn ( window , 'getComputedStyle' ) . mockReturnValue ( {
767+ borderTopWidth : '4px' ,
768+ borderBottomWidth : '6px' ,
769+ } as unknown as CSSStyleDeclaration )
770+
771+ const { rerender } = render (
772+ < MentionsInput autoResize value = "initial" onMentionsChange = { onMentionsChange } >
773+ < Mention trigger = "@" data = { data } />
774+ </ MentionsInput >
775+ )
776+
777+ const textarea = screen . getByRole ( 'combobox' )
778+ let scrollHeight = 0
779+ Object . defineProperty ( textarea , 'scrollHeight' , {
780+ configurable : true ,
781+ get : ( ) => scrollHeight ,
782+ } )
783+
784+ scrollHeight = 90
785+
786+ rerender (
787+ < MentionsInput
788+ autoResize
789+ value = "initial content extended"
790+ onMentionsChange = { onMentionsChange }
791+ >
792+ < Mention trigger = "@" data = { data } />
793+ </ MentionsInput >
794+ )
795+
796+ expect ( textarea . style . height ) . toBe ( '100px' )
797+ expect ( textarea . style . overflowY ) . toBe ( 'hidden' )
798+
799+ getComputedStyleSpy . mockRestore ( )
800+ } )
801+
802+ it ( 'clears inline sizing when autoResize is disabled' , ( ) => {
803+ const onMentionsChange = jest . fn ( )
804+ const { rerender } = render (
805+ < MentionsInput autoResize value = "draft" onMentionsChange = { onMentionsChange } >
806+ < Mention trigger = "@" data = { data } />
807+ </ MentionsInput >
808+ )
809+
810+ const textarea = screen . getByRole ( 'combobox' )
811+ let scrollHeight = 0
812+ Object . defineProperty ( textarea , 'scrollHeight' , {
813+ configurable : true ,
814+ get : ( ) => scrollHeight ,
815+ } )
816+
817+ scrollHeight = 72
818+
819+ rerender (
820+ < MentionsInput autoResize value = "draft updated" onMentionsChange = { onMentionsChange } >
821+ < Mention trigger = "@" data = { data } />
822+ </ MentionsInput >
823+ )
824+
825+ const computed = window . getComputedStyle ( textarea )
826+ const borderTop = Number . parseFloat ( computed . borderTopWidth || '0' )
827+ const borderBottom = Number . parseFloat ( computed . borderBottomWidth || '0' )
828+ expect ( Number . parseFloat ( textarea . style . height ) ) . toBe ( scrollHeight + borderTop + borderBottom )
829+ expect ( textarea . style . overflowY ) . toBe ( 'hidden' )
830+
831+ rerender (
832+ < MentionsInput autoResize = { false } value = "draft updated" onMentionsChange = { onMentionsChange } >
833+ < Mention trigger = "@" data = { data } />
834+ </ MentionsInput >
835+ )
836+
837+ expect ( textarea . style . height ) . toBe ( '' )
838+ expect ( textarea . style . overflowY ) . toBe ( '' )
839+ } )
840+ } )
841+
632842 describe ( 'custom cut/copy/paste' , ( ) => {
633843 const plainTextValue = "Hi First, \n\nlet's add Second to the conversation."
634844 const value = "Hi @[First](first), \n\nlet's add @[Second](second) to the conversation."
0 commit comments