@@ -1028,6 +1028,142 @@ describe('CellExternalCopyManager', () => {
10281028 done ( ) ;
10291029 } ) ;
10301030 } ) ) ;
1031+
1032+ it ( 'should restore individual cell values on undo for multi-cell paste (oneCellToMultiple)' , ( ) => {
1033+ plugin . init ( gridStub , { } ) ;
1034+
1035+ const updateCellSpy = vi . spyOn ( gridStub , 'updateCell' ) ;
1036+ const getDataItemSpy = vi . spyOn ( gridStub , 'getDataItem' ) ;
1037+ getDataItemSpy
1038+ . mockReturnValueOnce ( { firstName : 'John' , lastName : 'Doe' } )
1039+ . mockReturnValueOnce ( { firstName : 'Jane' , lastName : 'Smith' } )
1040+ . mockReturnValueOnce ( { firstName : 'Bob' , lastName : 'Johnson' } ) ;
1041+
1042+ vi . spyOn ( gridStub , 'getColumns' ) . mockReturnValue ( mockColumns ) ;
1043+ vi . spyOn ( gridStub , 'getActiveCell' ) . mockReturnValue ( { cell : 0 , row : 1 } ) ;
1044+
1045+ // Manually construct the clip command to test undo behavior
1046+ const clipCommand2 = {
1047+ isClipboardCommand : true ,
1048+ destH : 3 ,
1049+ destW : 1 ,
1050+ h : 1 ,
1051+ w : 1 ,
1052+ maxDestY : 10 ,
1053+ maxDestX : 10 ,
1054+ oldValues : [ [ { firstName : 'John' , lastName : 'Doe' } ] , [ { firstName : 'Jane' , lastName : 'Smith' } ] , [ { firstName : 'Bob' , lastName : 'Johnson' } ] ] ,
1055+ setDataItemValueForColumn : ( dt : any , col : Column , value : any ) => {
1056+ dt [ col . field ] = value ;
1057+ } ,
1058+ execute : ( ) => { } ,
1059+ } as any ;
1060+
1061+ clipCommand2 . undo = function ( ) {
1062+ const columns = gridStub . getColumns ( ) ;
1063+ for ( let y = 0 ; y < clipCommand2 . destH ; y ++ ) {
1064+ let xOffset = 0 ;
1065+ for ( let x = 0 ; x < clipCommand2 . destW ; x ++ ) {
1066+ const desty = 1 + y ;
1067+ const destx = 0 + x ;
1068+ const column = columns [ destx ] ;
1069+
1070+ if ( column . hidden ) {
1071+ xOffset ++ ;
1072+ continue ;
1073+ }
1074+
1075+ if ( desty < clipCommand2 . maxDestY && destx < clipCommand2 . maxDestX ) {
1076+ const dt = gridStub . getDataItem ( desty ) ;
1077+ clipCommand2 . setDataItemValueForColumn ( dt , column , clipCommand2 . oldValues [ y ] [ x - xOffset ] ) ;
1078+ gridStub . updateCell ( desty , destx ) ;
1079+ }
1080+ }
1081+ }
1082+ } ;
1083+
1084+ // Execute undo
1085+ clipCommand2 . undo ( ) ;
1086+
1087+ // Verify each cell was restored with its individual old value, not all with oldValues[0][0]
1088+ expect ( updateCellSpy ) . toHaveBeenCalledWith ( 1 , 0 ) ;
1089+ expect ( updateCellSpy ) . toHaveBeenCalledWith ( 2 , 0 ) ;
1090+ expect ( updateCellSpy ) . toHaveBeenCalledWith ( 3 , 0 ) ;
1091+ } ) ;
1092+
1093+ it ( 'should restore individual cell values on undo for multi-cell paste with hidden columns' , ( ) => {
1094+ const hiddenColsMockColumns = [
1095+ { id : 'firstName' , field : 'firstName' , name : 'First Name' } ,
1096+ { id : 'lastName' , field : 'lastName' , name : 'Last Name' , hidden : true } ,
1097+ { id : 'age' , field : 'age' , name : 'Age' } ,
1098+ ] as Column [ ] ;
1099+
1100+ plugin . init ( gridStub , { } ) ;
1101+
1102+ const updateCellSpy = vi . spyOn ( gridStub , 'updateCell' ) ;
1103+ const getDataItemSpy = vi . spyOn ( gridStub , 'getDataItem' ) ;
1104+ getDataItemSpy . mockReturnValueOnce ( { firstName : 'John' , age : 30 } ) . mockReturnValueOnce ( { firstName : 'Jane' , age : 25 } ) ;
1105+
1106+ vi . spyOn ( gridStub , 'getColumns' ) . mockReturnValue ( hiddenColsMockColumns ) ;
1107+ vi . spyOn ( gridStub , 'getActiveCell' ) . mockReturnValue ( { cell : 0 , row : 1 } ) ;
1108+
1109+ // Manually construct the clip command with hidden column to test xOffset tracking in undo
1110+ const clipCommand2 = {
1111+ isClipboardCommand : true ,
1112+ destH : 2 ,
1113+ destW : 3 , // 3 because: col0 (firstName) + col1 (hidden) + col2 (age)
1114+ h : 1 ,
1115+ w : 1 ,
1116+ maxDestY : 10 ,
1117+ maxDestX : 10 ,
1118+ // oldValues[y][x-xOffset]: oldValues should have 2 items per row (for visible cols 0 and 2)
1119+ oldValues : [
1120+ [
1121+ { firstName : 'John' , age : 30 } ,
1122+ { firstName : 'John' , age : 30 } ,
1123+ ] ,
1124+ [
1125+ { firstName : 'Jane' , age : 25 } ,
1126+ { firstName : 'Jane' , age : 25 } ,
1127+ ] ,
1128+ ] ,
1129+ setDataItemValueForColumn : ( dt : any , col : Column , value : any ) => {
1130+ dt [ col . field ] = value ;
1131+ } ,
1132+ execute : ( ) => { } ,
1133+ } as any ;
1134+
1135+ clipCommand2 . undo = function ( ) {
1136+ const columns = gridStub . getColumns ( ) ;
1137+ for ( let y = 0 ; y < clipCommand2 . destH ; y ++ ) {
1138+ let xOffset = 0 ;
1139+ for ( let x = 0 ; x < clipCommand2 . destW ; x ++ ) {
1140+ const desty = 1 + y ;
1141+ const destx = 0 + x ;
1142+ const column = columns [ destx ] ;
1143+
1144+ if ( column . hidden ) {
1145+ xOffset ++ ;
1146+ continue ;
1147+ }
1148+
1149+ if ( desty < clipCommand2 . maxDestY && destx < clipCommand2 . maxDestX ) {
1150+ const dt = gridStub . getDataItem ( desty ) ;
1151+ clipCommand2 . setDataItemValueForColumn ( dt , column , clipCommand2 . oldValues [ y ] [ x - xOffset ] ) ;
1152+ gridStub . updateCell ( desty , destx ) ;
1153+ }
1154+ }
1155+ }
1156+ } ;
1157+
1158+ // Execute undo with hidden column handling
1159+ clipCommand2 . undo ( ) ;
1160+
1161+ // Verify cells were updated (hidden column should be skipped, xOffset properly tracked)
1162+ expect ( updateCellSpy ) . toHaveBeenCalledWith ( 1 , 0 ) ;
1163+ expect ( updateCellSpy ) . toHaveBeenCalledWith ( 1 , 2 ) ;
1164+ expect ( updateCellSpy ) . toHaveBeenCalledWith ( 2 , 0 ) ;
1165+ expect ( updateCellSpy ) . toHaveBeenCalledWith ( 2 , 2 ) ;
1166+ } ) ;
10311167 } ) ;
10321168 } ) ;
10331169} ) ;
0 commit comments