@@ -15,6 +15,7 @@ import {
1515 normalizeFramePr ,
1616 normalizeDropCap ,
1717 computeParagraphAttrs ,
18+ resolveEffectiveParagraphDirection ,
1819 computeRunAttrs ,
1920 hasExplicitParagraphRunProperties ,
2021} from './paragraph.js' ;
@@ -273,7 +274,117 @@ describe('computeParagraphAttrs', () => {
273274 const { paragraphAttrs } = computeParagraphAttrs ( paragraph as never ) ;
274275
275276 expect ( paragraphAttrs . direction ) . toBe ( 'rtl' ) ;
276- expect ( paragraphAttrs . rtl ) . toBe ( true ) ;
277+ } ) ;
278+
279+ it ( 'uses section direction fallback when paragraph direction is not explicit' , ( ) => {
280+ const paragraph : PMNode = {
281+ type : { name : 'paragraph' } ,
282+ attrs : {
283+ paragraphProperties : { } ,
284+ } ,
285+ } ;
286+
287+ const converterContext = {
288+ sectionDirection : 'rtl' ,
289+ translatedNumbering : { } ,
290+ translatedLinkedStyles : { docDefaults : { } , styles : { } } ,
291+ tableInfo : null ,
292+ } ;
293+
294+ const { paragraphAttrs } = computeParagraphAttrs ( paragraph as never , converterContext as never ) ;
295+ expect ( paragraphAttrs . direction ) . toBe ( 'rtl' ) ;
296+ } ) ;
297+ } ) ;
298+
299+ describe ( 'resolveEffectiveParagraphDirection' , ( ) => {
300+ it ( 'prefers resolved paragraph rightToLeft over section direction' , ( ) => {
301+ const paragraph : PMNode = {
302+ type : { name : 'paragraph' } ,
303+ attrs : {
304+ paragraphProperties : {
305+ rightToLeft : true ,
306+ } ,
307+ } ,
308+ } ;
309+
310+ const direction = resolveEffectiveParagraphDirection ( paragraph as never , { rightToLeft : true } as never , 'ltr' ) ;
311+ expect ( direction ) . toBe ( 'rtl' ) ;
312+ } ) ;
313+
314+ it ( 'uses section direction when paragraph direction is not explicit' , ( ) => {
315+ const paragraph : PMNode = {
316+ type : { name : 'paragraph' } ,
317+ attrs : {
318+ paragraphProperties : { } ,
319+ } ,
320+ } ;
321+
322+ const direction = resolveEffectiveParagraphDirection ( paragraph as never , { } as never , 'rtl' ) ;
323+ expect ( direction ) . toBe ( 'rtl' ) ;
324+ } ) ;
325+
326+ it ( 'infers rtl when all runs with explicit direction are rtl' , ( ) => {
327+ const paragraph : PMNode = {
328+ type : { name : 'paragraph' } ,
329+ content : [
330+ { type : 'run' , attrs : { runProperties : { rightToLeft : true } } , content : [ { type : 'text' , text : 'אבג' } ] } ,
331+ { type : 'run' , attrs : { runProperties : { rightToLeft : true } } , content : [ { type : 'text' , text : 'דהו' } ] } ,
332+ ] ,
333+ } ;
334+
335+ const direction = resolveEffectiveParagraphDirection ( paragraph as never , { } as never ) ;
336+ expect ( direction ) . toBe ( 'rtl' ) ;
337+ } ) ;
338+
339+ it ( 'infers ltr when explicit ltr runs are the majority' , ( ) => {
340+ const paragraph : PMNode = {
341+ type : { name : 'paragraph' } ,
342+ content : [
343+ { type : 'run' , attrs : { runProperties : { rightToLeft : true } } , content : [ { type : 'text' , text : 'אבג' } ] } ,
344+ { type : 'run' , attrs : { runProperties : { rightToLeft : false } } , content : [ { type : 'text' , text : 'abc' } ] } ,
345+ { type : 'run' , attrs : { runProperties : { rightToLeft : false } } , content : [ { type : 'text' , text : 'def' } ] } ,
346+ ] ,
347+ } ;
348+
349+ const direction = resolveEffectiveParagraphDirection ( paragraph as never , { } as never ) ;
350+ expect ( direction ) . toBe ( 'ltr' ) ;
351+ } ) ;
352+
353+ it ( 'infers rtl when explicit rtl runs are the majority' , ( ) => {
354+ const paragraph : PMNode = {
355+ type : { name : 'paragraph' } ,
356+ content : [
357+ { type : 'run' , attrs : { runProperties : { rightToLeft : false } } , content : [ { type : 'text' , text : 'abc' } ] } ,
358+ { type : 'run' , attrs : { runProperties : { rightToLeft : true } } , content : [ { type : 'text' , text : 'אבג' } ] } ,
359+ { type : 'run' , attrs : { runProperties : { rightToLeft : true } } , content : [ { type : 'text' , text : 'דהו' } ] } ,
360+ ] ,
361+ } ;
362+
363+ const direction = resolveEffectiveParagraphDirection ( paragraph as never , { } as never ) ;
364+ expect ( direction ) . toBe ( 'rtl' ) ;
365+ } ) ;
366+
367+ it ( 'uses first explicit run direction as tie-breaker for mixed runs' , ( ) => {
368+ const paragraph : PMNode = {
369+ type : { name : 'paragraph' } ,
370+ content : [
371+ { type : 'run' , attrs : { runProperties : { rightToLeft : true } } , content : [ { type : 'text' , text : 'אבג' } ] } ,
372+ { type : 'run' , attrs : { runProperties : { rightToLeft : false } } , content : [ { type : 'text' , text : 'abc' } ] } ,
373+ ] ,
374+ } ;
375+
376+ const direction = resolveEffectiveParagraphDirection ( paragraph as never , { } as never ) ;
377+ expect ( direction ) . toBe ( 'rtl' ) ;
378+ } ) ;
379+
380+ it ( 'returns undefined when no direction signal exists' , ( ) => {
381+ const paragraph : PMNode = {
382+ type : { name : 'paragraph' } ,
383+ content : [ { type : 'run' , attrs : { runProperties : { } } , content : [ { type : 'text' , text : 'plain text' } ] } ] ,
384+ } ;
385+
386+ const direction = resolveEffectiveParagraphDirection ( paragraph as never , { } as never ) ;
387+ expect ( direction ) . toBeUndefined ( ) ;
277388 } ) ;
278389} ) ;
279390
0 commit comments