@@ -646,4 +646,225 @@ describe("MDX Code Placeholder Loader", () => {
646646 expect ( pushedResult ) . toContain ( "قم بتطبيق" ) ;
647647 } ) ;
648648 } ) ;
649+
650+ describe ( "raw code outside fences" , ( ) => {
651+ it ( "should handle raw JavaScript code outside fences" , async ( ) => {
652+ const loader = createMdxCodePlaceholderLoader ( ) ;
653+ loader . setDefaultLocale ( "en" ) ;
654+
655+ // Test case matching user's file structure - raw JS between JSX components
656+ const md = dedent `
657+ </Tabs>
658+
659+ // Attach to button click
660+ document.getElementById('executeBtn')?.addEventListener('click', executeClientSideWorkflow);
661+
662+ <Callout type="warning">
663+ Content here
664+ </Callout>
665+ ` ;
666+
667+ const pulled = await loader . pull ( "en" , md ) ;
668+ const pushed = await loader . push ( "en" , pulled ) ;
669+
670+ // Should round-trip correctly
671+ expect ( pushed ) . toBe ( md ) ;
672+ } ) ;
673+
674+ it ( "should handle mixed code blocks and raw code" , async ( ) => {
675+ const loader = createMdxCodePlaceholderLoader ( ) ;
676+ loader . setDefaultLocale ( "en" ) ;
677+
678+ const md = dedent `
679+ Here's a code block:
680+
681+ \`\`\`typescript
682+ const x = 1;
683+ \`\`\`
684+
685+ Now some raw code outside:
686+ // This is outside
687+ const y = 2;
688+
689+ And another block:
690+
691+ \`\`\`javascript
692+ const z = 3;
693+ \`\`\`
694+ ` ;
695+
696+ const pulled = await loader . pull ( "en" , md ) ;
697+ const pushed = await loader . push ( "en" , pulled ) ;
698+
699+ // Should preserve raw code outside fences
700+ expect ( pushed ) . toContain ( "// This is outside" ) ;
701+ expect ( pushed ) . toContain ( "const y = 2;" ) ;
702+ } ) ;
703+
704+ it ( "should handle code blocks with extra blank lines added by translation" , async ( ) => {
705+ const loader = createMdxCodePlaceholderLoader ( ) ;
706+ loader . setDefaultLocale ( "en" ) ;
707+
708+ // English source - no extra blank lines
709+ const enMd = dedent `
710+ <Tab value="npm">
711+ \`\`\`bash
712+ npm install
713+ \`\`\`
714+ </Tab>
715+ ` ;
716+
717+ // Pull English to establish placeholders
718+ const enPulled = await loader . pull ( "en" , enMd ) ;
719+
720+ // German translation with extra blank lines (simulating AI translation behavior)
721+ const deMd = dedent `
722+ <Tab value="npm">
723+
724+ \`\`\`bash
725+ npm install
726+ \`\`\`
727+
728+ </Tab>
729+ ` ;
730+
731+ // Pull German version
732+ const dePulled = await loader . pull ( "de" , deMd ) ;
733+
734+ // Push back - should restore code blocks correctly
735+ const dePushed = await loader . push ( "de" , dePulled ) ;
736+
737+ // The code block should be present and not replaced with placeholder
738+ expect ( dePushed ) . toContain ( "```bash" ) ;
739+ expect ( dePushed ) . toContain ( "npm install" ) ;
740+ expect ( dePushed ) . not . toMatch ( / - - - C O D E - P L A C E H O L D E R - / ) ;
741+ } ) ;
742+
743+ it ( "should preserve double newlines around placeholders for section splitting" , async ( ) => {
744+ const loader = createMdxCodePlaceholderLoader ( ) ;
745+ loader . setDefaultLocale ( "en" ) ;
746+
747+ // Test that placeholders maintain double newlines so section-split works correctly
748+ const md = dedent `
749+ Text before.
750+
751+ \`\`\`typescript
752+ code1
753+ \`\`\`
754+
755+ Text between.
756+
757+ \`\`\`javascript
758+ code2
759+ \`\`\`
760+
761+ Text after.
762+ ` ;
763+
764+ const pulled = await loader . pull ( "en" , md ) ;
765+
766+ // Verify placeholders are surrounded by double newlines for proper section splitting
767+ const placeholders = pulled . match ( / - - - C O D E - P L A C E H O L D E R - [ a - f 0 - 9 ] + - - - / g) ;
768+ expect ( placeholders ) . toHaveLength ( 2 ) ;
769+
770+ // Check that each placeholder has double newlines around it
771+ for ( const placeholder of placeholders ! ) {
772+ // Should have \n\n before (except at start) and \n\n after (except at end)
773+ const placeholderIndex = pulled . indexOf ( placeholder ) ;
774+
775+ // Check for double newline after (unless at end)
776+ const afterPlaceholder = pulled . substring (
777+ placeholderIndex + placeholder . length ,
778+ placeholderIndex + placeholder . length + 2 ,
779+ ) ;
780+ if ( placeholderIndex + placeholder . length < pulled . length - 2 ) {
781+ expect ( afterPlaceholder ) . toBe ( "\n\n" ) ;
782+ }
783+ }
784+
785+ // Ensure we can split on \n\n and get separate sections
786+ const sections = pulled . split ( "\n\n" ) . filter ( Boolean ) ;
787+ expect ( sections . length ) . toBeGreaterThanOrEqual ( 5 ) ; // Text + placeholder + text + placeholder + text
788+ } ) ;
789+ } ) ;
790+ } ) ;
791+
792+ describe ( "adjacent code blocks bug" , ( ) => {
793+ it ( "should handle closing fence followed immediately by opening fence" , async ( ) => {
794+ const loader = createMdxCodePlaceholderLoader ( ) ;
795+ loader . setDefaultLocale ( "en" ) ;
796+
797+ // This reproduces the actual bug from the user's file
798+ const md = dedent `
799+ \`\`\`typescript
800+ function example() {
801+ return true;
802+ }
803+ \`\`\`
804+
805+ \`\`\`typescript
806+ import { Something } from 'somewhere';
807+ \`\`\`
808+ ` ;
809+
810+ const pulled = await loader . pull ( "en" , md ) ;
811+
812+ console . log ( "PULLED CONTENT:" ) ;
813+ console . log ( pulled ) ;
814+ console . log ( "---" ) ;
815+
816+ // The bug: placeholder is concatenated with "typescript" from next block
817+ const bugPattern = / - - - C O D E - P L A C E H O L D E R - [ a - f 0 - 9 ] + - - - t y p e s c r i p t / ;
818+ expect ( pulled ) . not . toMatch ( bugPattern ) ;
819+
820+ // Should have proper separation
821+ expect ( pulled ) . toMatch (
822+ / - - - C O D E - P L A C E H O L D E R - [ a - f 0 - 9 ] + - - - \n \n - - - C O D E - P L A C E H O L D E R - [ a - f 0 - 9 ] + - - - / ,
823+ ) ;
824+ } ) ;
825+ } ) ;
826+
827+ describe ( "$ special character handling in replacement functions" , ( ) => {
828+ it ( "should preserve $ characters in ensureTrailingFenceNewline" , async ( ) => {
829+ const loader = createMdxCodePlaceholderLoader ( ) ;
830+ loader . setDefaultLocale ( "en" ) ;
831+
832+ // Tests fix for lines 38, 68: replaceAll(match, () => replacement)
833+ // Code block with $ that would trigger special replacement behavior if not using function replacer
834+ const content = dedent `
835+ Some text
836+ \`\`\`js
837+ console.log('Current period cost: $' + amount);
838+ const template = \`Price: $\${price}\`;
839+ \`\`\`
840+ More text
841+ ` ;
842+
843+ const pulled = await loader . pull ( "en" , content ) ;
844+ const pushed = await loader . push ( "en" , pulled ) ;
845+
846+ // All $ characters should be preserved exactly
847+ expect ( pushed ) . toContain ( "console.log('Current period cost: $' + amount);" ) ;
848+ expect ( pushed ) . toContain ( "const template = `Price: $" ) ;
849+ } ) ;
850+
851+ it ( "should preserve $ characters in ensureSurroundingImageNewlines" , async ( ) => {
852+ const loader = createMdxCodePlaceholderLoader ( ) ;
853+ loader . setDefaultLocale ( "en" ) ;
854+
855+ // Tests fix for line 38: replaceAll(match, () => replacement) in image handling
856+ // Image with $ in URL and alt text that would break with string replacer
857+ const content = dedent `
858+ Here is an image:
859+ 
860+ End of text
861+ ` ;
862+
863+ const pulled = await loader . pull ( "en" , content ) ;
864+ const pushed = await loader . push ( "en" , pulled ) ;
865+
866+ // All $ characters in URL and alt text should be preserved
867+ expect ( pushed ) . toContain ( "![Price: $100]" ) ;
868+ expect ( pushed ) . toContain ( "price=$500¤cy=$USD" ) ;
869+ } ) ;
649870} ) ;
0 commit comments