@@ -729,6 +729,120 @@ describe('vModel', () => {
729729 expect ( bar . checked ) . toEqual ( false )
730730 } )
731731
732+ it ( 'should not update DOM unnecessarily' , async ( ) => {
733+ const component = defineComponent ( {
734+ data ( ) {
735+ return { value : true }
736+ } ,
737+ render ( ) {
738+ return [
739+ withVModel (
740+ h ( 'input' , {
741+ type : 'checkbox' ,
742+ 'onUpdate:modelValue' : setValue . bind ( this ) ,
743+ } ) ,
744+ this . value ,
745+ ) ,
746+ ]
747+ } ,
748+ } )
749+ render ( h ( component ) , root )
750+
751+ const input = root . querySelector ( 'input' )
752+ const data = root . _vnode . component . data
753+
754+ const setCheckedSpy = vi . spyOn ( input , 'checked' , 'set' )
755+
756+ // Trigger a change event without actually changing the value
757+ triggerEvent ( 'change' , input )
758+ await nextTick ( )
759+ expect ( data . value ) . toEqual ( true )
760+ expect ( setCheckedSpy ) . not . toHaveBeenCalled ( )
761+
762+ // Change the value and trigger a change event
763+ input . checked = false
764+ triggerEvent ( 'change' , input )
765+ await nextTick ( )
766+ expect ( data . value ) . toEqual ( false )
767+ expect ( setCheckedSpy ) . toHaveBeenCalledTimes ( 1 )
768+
769+ setCheckedSpy . mockClear ( )
770+
771+ data . value = false
772+ await nextTick ( )
773+ expect ( input . checked ) . toEqual ( false )
774+ expect ( setCheckedSpy ) . not . toHaveBeenCalled ( )
775+
776+ data . value = true
777+ await nextTick ( )
778+ expect ( input . checked ) . toEqual ( true )
779+ expect ( setCheckedSpy ) . toHaveBeenCalledTimes ( 1 )
780+ } )
781+
782+ it ( 'should handle array values correctly without unnecessary updates' , async ( ) => {
783+ const component = defineComponent ( {
784+ data ( ) {
785+ return { value : [ 'foo' ] }
786+ } ,
787+ render ( ) {
788+ return [
789+ withVModel (
790+ h ( 'input' , {
791+ type : 'checkbox' ,
792+ value : 'foo' ,
793+ 'onUpdate:modelValue' : setValue . bind ( this ) ,
794+ } ) ,
795+ this . value ,
796+ ) ,
797+ withVModel (
798+ h ( 'input' , {
799+ type : 'checkbox' ,
800+ value : 'bar' ,
801+ 'onUpdate:modelValue' : setValue . bind ( this ) ,
802+ } ) ,
803+ this . value ,
804+ ) ,
805+ ]
806+ } ,
807+ } )
808+ render ( h ( component ) , root )
809+
810+ const [ foo , bar ] = root . querySelectorAll ( 'input' )
811+ const data = root . _vnode . component . data
812+
813+ const setCheckedSpyFoo = vi . spyOn ( foo , 'checked' , 'set' )
814+ const setCheckedSpyBar = vi . spyOn ( bar , 'checked' , 'set' )
815+
816+ expect ( foo . checked ) . toEqual ( true )
817+ expect ( bar . checked ) . toEqual ( false )
818+
819+ triggerEvent ( 'change' , foo )
820+ await nextTick ( )
821+ expect ( data . value ) . toEqual ( [ 'foo' ] )
822+ expect ( setCheckedSpyFoo ) . not . toHaveBeenCalled ( )
823+
824+ bar . checked = true
825+ triggerEvent ( 'change' , bar )
826+ await nextTick ( )
827+ expect ( data . value ) . toEqual ( [ 'foo' , 'bar' ] )
828+ expect ( setCheckedSpyBar ) . toHaveBeenCalledTimes ( 1 )
829+
830+ setCheckedSpyFoo . mockClear ( )
831+ setCheckedSpyBar . mockClear ( )
832+
833+ data . value = [ 'foo' , 'bar' ]
834+ await nextTick ( )
835+ expect ( setCheckedSpyFoo ) . not . toHaveBeenCalled ( )
836+ expect ( setCheckedSpyBar ) . not . toHaveBeenCalled ( )
837+
838+ data . value = [ 'bar' ]
839+ await nextTick ( )
840+ expect ( setCheckedSpyFoo ) . toHaveBeenCalledTimes ( 1 )
841+ expect ( setCheckedSpyBar ) . not . toHaveBeenCalled ( )
842+ expect ( foo . checked ) . toEqual ( false )
843+ expect ( bar . checked ) . toEqual ( true )
844+ } )
845+
732846 it ( 'should work with radio' , async ( ) => {
733847 const component = defineComponent ( {
734848 data ( ) {
0 commit comments