@@ -780,6 +780,260 @@ function five() {
780780}` )
781781 }
782782 } )
783+
784+ it ( "should handle multiple search/replace blocks with proper indentation #1559" , async ( ) => {
785+ // This test verifies the fix for GitHub issue #1559
786+ // where applying a diff with multiple search/replace blocks previously resulted in incorrect indentation
787+
788+ // Test case 1: Simple scenario with two search/replace blocks
789+ {
790+ const originalContent = `function test() {
791+ const a = 1;
792+ const b = 2;
793+
794+ // Some comment
795+ return a + b;
796+ }`
797+
798+ const diffContent = `\<<<<<<< SEARCH
799+ :start_line:2
800+ :end_line:3
801+ -------
802+ const a = 1;
803+ const b = 2;
804+ =======
805+ const x = 10;
806+ const y = 20;
807+ >>>>>>> REPLACE
808+
809+ <<<<<<< SEARCH
810+ :start_line:6
811+ -------
812+ return a + b;
813+ =======
814+ return x + y;
815+ >>>>>>> REPLACE`
816+
817+ const expectedContent = `function test() {
818+ const x = 10;
819+ const y = 20;
820+
821+ // Some comment
822+ return x + y;
823+ }`
824+
825+ const result = await strategy . applyDiff ( originalContent , diffContent )
826+
827+ // Verify that the operation succeeds
828+ expect ( result . success ) . toBe ( true )
829+
830+ // Verify the content matches what we expect
831+ if ( result . success ) {
832+ expect ( result . content ) . toBe ( expectedContent )
833+ }
834+ }
835+
836+ // Test case 2: Complex scenario that resembles the original bug report
837+ {
838+ const complexOriginalContent = ` const BUTTON_HEIGHT=100
839+ const scrollRect = scrollContainer.getBoundingClientRect()
840+ const isPartiallyVisible = rectCodeBlock.top < (scrollRect.bottom - BUTTON_HEIGHT) && rectCodeBlock.bottom >= (scrollRect.top + BUTTON_HEIGHT)
841+
842+ // Calculate margin from existing padding in the component
843+ const computedStyle = window.getComputedStyle(codeBlock)
844+ const paddingValue = parseInt(computedStyle.getPropertyValue("padding") || "0", 10)
845+ const margin =
846+ paddingValue > 0 ? paddingValue : parseInt(computedStyle.getPropertyValue("padding-top") || "0", 10)
847+
848+ // Get wrapper height dynamically
849+ let wrapperHeight
850+ if (copyWrapper) {
851+ const copyRect = copyWrapper.getBoundingClientRect()
852+ // If height is 0 due to styling, estimate from children
853+ if (copyRect.height > 0) {
854+ wrapperHeight = copyRect.height
855+ } else if (copyWrapper.children.length > 0) {
856+ // Try to get height from the button inside
857+ const buttonRect = copyWrapper.children[0].getBoundingClientRect()
858+ const buttonStyle = window.getComputedStyle(copyWrapper.children[0] as Element)
859+ const buttonPadding =
860+ parseInt(buttonStyle.getPropertyValue("padding-top") || "0", 10) +
861+ parseInt(buttonStyle.getPropertyValue("padding-bottom") || "0", 10)
862+ wrapperHeight = buttonRect.height + buttonPadding
863+ }
864+ }
865+
866+ // If we still don't have a height, calculate from font size
867+ if (!wrapperHeight) {
868+ const fontSize = parseInt(window.getComputedStyle(document.body).getPropertyValue("font-size"), 10)
869+ wrapperHeight = fontSize * 2.5 // Approximate button height based on font size
870+ }`
871+
872+ const complexDiffContent = `\<<<<<<< SEARCH
873+ :start_line:1
874+ :end_line:3
875+ -------
876+ const BUTTON_HEIGHT=100
877+ const scrollRect = scrollContainer.getBoundingClientRect()
878+ const isPartiallyVisible = rectCodeBlock.top < (scrollRect.bottom - BUTTON_HEIGHT) && rectCodeBlock.bottom >= (scrollRect.top + BUTTON_HEIGHT)
879+
880+ =======
881+ const scrollRect = scrollContainer.getBoundingClientRect()
882+
883+ // Get wrapper height dynamically
884+ let wrapperHeight
885+ if (copyWrapper) {
886+ const copyRect = copyWrapper.getBoundingClientRect()
887+ // If height is 0 due to styling, estimate from children
888+ if (copyRect.height > 0) {
889+ wrapperHeight = copyRect.height
890+ } else if (copyWrapper.children.length > 0) {
891+ // Try to get height from the button inside
892+ const buttonRect = copyWrapper.children[0].getBoundingClientRect()
893+ const buttonStyle = window.getComputedStyle(copyWrapper.children[0] as Element)
894+ const buttonPadding =
895+ parseInt(buttonStyle.getPropertyValue("padding-top") || "0", 10) +
896+ parseInt(buttonStyle.getPropertyValue("padding-bottom") || "0", 10)
897+ wrapperHeight = buttonRect.height + buttonPadding
898+ }
899+ }
900+
901+ // If we still don't have a height, calculate from font size
902+ if (!wrapperHeight) {
903+ const fontSize = parseInt(window.getComputedStyle(document.body).getPropertyValue("font-size"), 10)
904+ wrapperHeight = fontSize * 2.5 // Approximate button height based on font size
905+ }
906+
907+ const isPartiallyVisible = rectCodeBlock.top < (scrollRect.bottom - wrapperHeight) && rectCodeBlock.bottom >= (scrollRect.top + wrapperHeight)
908+
909+ >>>>>>> REPLACE
910+
911+ <<<<<<< SEARCH
912+ :start_line:11
913+ :end_line:30
914+ -------
915+ // Get wrapper height dynamically
916+ let wrapperHeight
917+ if (copyWrapper) {
918+ const copyRect = copyWrapper.getBoundingClientRect()
919+ // If height is 0 due to styling, estimate from children
920+ if (copyRect.height > 0) {
921+ wrapperHeight = copyRect.height
922+ } else if (copyWrapper.children.length > 0) {
923+ // Try to get height from the button inside
924+ const buttonRect = copyWrapper.children[0].getBoundingClientRect()
925+ const buttonStyle = window.getComputedStyle(copyWrapper.children[0] as Element)
926+ const buttonPadding =
927+ parseInt(buttonStyle.getPropertyValue("padding-top") || "0", 10) +
928+ parseInt(buttonStyle.getPropertyValue("padding-bottom") || "0", 10)
929+ wrapperHeight = buttonRect.height + buttonPadding
930+ }
931+ }
932+
933+ // If we still don't have a height, calculate from font size
934+ if (!wrapperHeight) {
935+ const fontSize = parseInt(window.getComputedStyle(document.body).getPropertyValue("font-size"), 10)
936+ wrapperHeight = fontSize * 2.5 // Approximate button height based on font size
937+ }
938+
939+ =======
940+ >>>>>>> REPLACE`
941+
942+ // Execute the diff application
943+ const complexResult = await strategy . applyDiff ( complexOriginalContent , complexDiffContent )
944+
945+ // Log the actual result for debugging
946+ console . log (
947+ "Complex test actual result:" ,
948+ complexResult . success ? complexResult . content : complexResult . error ,
949+ )
950+
951+ // Verify that the operation succeeds
952+ expect ( complexResult . success ) . toBe ( true )
953+
954+ // Instead of comparing exact content, we'll verify key aspects of the result
955+ if ( complexResult . success ) {
956+ const content = complexResult . content
957+
958+ // Check that the content starts with the expected scrollRect line
959+ expect ( content . startsWith ( " const scrollRect = scrollContainer.getBoundingClientRect()" ) ) . toBe (
960+ true ,
961+ )
962+
963+ // Check that the content contains the wrapperHeight variable declaration
964+ expect ( content . includes ( " let wrapperHeight" ) ) . toBe ( true )
965+
966+ // Check that the content contains the isPartiallyVisible line with wrapperHeight
967+ expect (
968+ content . includes (
969+ " const isPartiallyVisible = rectCodeBlock.top < (scrollRect.bottom - wrapperHeight) && rectCodeBlock.bottom >= (scrollRect.top + wrapperHeight)" ,
970+ ) ,
971+ ) . toBe ( true )
972+
973+ // Check that the content contains the margin calculation
974+ expect ( content . includes ( " const margin =" ) ) . toBe ( true )
975+ expect (
976+ content . includes (
977+ ' paddingValue > 0 ? paddingValue : parseInt(computedStyle.getPropertyValue("padding-top") || "0", 10)' ,
978+ ) ,
979+ ) . toBe ( true )
980+ }
981+ }
982+ // This test verifies the fix for GitHub issue #1559
983+ // where applying a diff with multiple search/replace blocks previously resulted in incorrect indentation
984+
985+ // Create a very simple test case with minimal content
986+ const originalContent = `function test() {
987+ const a = 1;
988+ const b = 2;
989+
990+ // Some comment
991+ return a + b;
992+ }`
993+
994+ // Create a diff with two search/replace blocks
995+ const diffContent = `\<<<<<<< SEARCH
996+ :start_line:2
997+ :end_line:3
998+ -------
999+ const a = 1;
1000+ const b = 2;
1001+ =======
1002+ const x = 10;
1003+ const y = 20;
1004+ >>>>>>> REPLACE
1005+
1006+ <<<<<<< SEARCH
1007+ :start_line:6
1008+ -------
1009+ return a + b;
1010+ =======
1011+ return x + y;
1012+ >>>>>>> REPLACE`
1013+
1014+ // The expected content after applying the diff
1015+ const expectedContent = `function test() {
1016+ const x = 10;
1017+ const y = 20;
1018+
1019+ // Some comment
1020+ return x + y;
1021+ }`
1022+
1023+ // Execute the diff application
1024+ const result = await strategy . applyDiff ( originalContent , diffContent )
1025+
1026+ // Log the actual result for debugging
1027+ console . log ( "Actual result:" , result . success ? result . content : result . error )
1028+
1029+ // Verify that the operation succeeds
1030+ expect ( result . success ) . toBe ( true )
1031+
1032+ // Verify the content matches what we expect
1033+ if ( result . success ) {
1034+ expect ( result . content ) . toBe ( expectedContent )
1035+ }
1036+ } )
7831037 } )
7841038
7851039 describe ( "line number stripping" , ( ) => {
0 commit comments