@@ -10,8 +10,18 @@ import {
1010} from './features/paragraph-borders/index.js' ;
1111
1212/** Helper to create BetweenBorderInfo for tests that previously passed a boolean. */
13- const betweenOn : BetweenBorderInfo = { showBetweenBorder : true , suppressTopBorder : false , gapBelow : 0 } ;
14- const betweenOff : BetweenBorderInfo = { showBetweenBorder : false , suppressTopBorder : false , gapBelow : 0 } ;
13+ const betweenOn : BetweenBorderInfo = {
14+ showBetweenBorder : true ,
15+ suppressTopBorder : false ,
16+ suppressBottomBorder : false ,
17+ gapBelow : 0 ,
18+ } ;
19+ const betweenOff : BetweenBorderInfo = {
20+ showBetweenBorder : false ,
21+ suppressTopBorder : false ,
22+ suppressBottomBorder : false ,
23+ gapBelow : 0 ,
24+ } ;
1525import { createDomPainter } from './index.js' ;
1626import type {
1727 ParagraphBorders ,
@@ -258,7 +268,12 @@ describe('applyParagraphBorderStyles — between borders', () => {
258268 left : { style : 'solid' , width : 1 , color : '#000' } ,
259269 right : { style : 'solid' , width : 1 , color : '#000' } ,
260270 } ;
261- const info : BetweenBorderInfo = { showBetweenBorder : false , suppressTopBorder : true , gapBelow : 0 } ;
271+ const info : BetweenBorderInfo = {
272+ showBetweenBorder : false ,
273+ suppressTopBorder : true ,
274+ suppressBottomBorder : false ,
275+ gapBelow : 0 ,
276+ } ;
262277 applyParagraphBorderStyles ( e , borders , info ) ;
263278 expect ( e . style . getPropertyValue ( 'border-top-style' ) ) . toBe ( '' ) ;
264279 expect ( e . style . getPropertyValue ( 'border-left-style' ) ) . toBe ( 'solid' ) ;
@@ -274,6 +289,49 @@ describe('applyParagraphBorderStyles — between borders', () => {
274289 expect ( e . style . getPropertyValue ( 'border-top-style' ) ) . toBe ( 'dashed' ) ;
275290 expect ( e . style . getPropertyValue ( 'border-top-width' ) ) . toBe ( '3px' ) ;
276291 } ) ;
292+
293+ // --- suppressBottomBorder (nil/none between groups) ---
294+ it ( 'skips bottom border when suppressBottomBorder is true' , ( ) => {
295+ const e = el ( ) ;
296+ const borders : ParagraphBorders = {
297+ top : { style : 'solid' , width : 1 , color : '#000' } ,
298+ bottom : { style : 'solid' , width : 2 , color : '#F00' } ,
299+ left : { style : 'solid' , width : 1 , color : '#000' } ,
300+ right : { style : 'solid' , width : 1 , color : '#000' } ,
301+ } ;
302+ const info : BetweenBorderInfo = {
303+ showBetweenBorder : false ,
304+ suppressTopBorder : false ,
305+ suppressBottomBorder : true ,
306+ gapBelow : 10 ,
307+ } ;
308+ applyParagraphBorderStyles ( e , borders , info ) ;
309+ expect ( e . style . getPropertyValue ( 'border-top-style' ) ) . toBe ( 'solid' ) ;
310+ expect ( e . style . getPropertyValue ( 'border-left-style' ) ) . toBe ( 'solid' ) ;
311+ expect ( e . style . getPropertyValue ( 'border-right-style' ) ) . toBe ( 'solid' ) ;
312+ expect ( e . style . getPropertyValue ( 'border-bottom-style' ) ) . toBe ( '' ) ;
313+ } ) ;
314+
315+ it ( 'suppresses both top and bottom for middle fragment in nil/none group' , ( ) => {
316+ const e = el ( ) ;
317+ const borders : ParagraphBorders = {
318+ top : { style : 'solid' , width : 1 , color : '#000' } ,
319+ bottom : { style : 'solid' , width : 1 , color : '#000' } ,
320+ left : { style : 'solid' , width : 1 , color : '#000' } ,
321+ right : { style : 'solid' , width : 1 , color : '#000' } ,
322+ } ;
323+ const info : BetweenBorderInfo = {
324+ showBetweenBorder : false ,
325+ suppressTopBorder : true ,
326+ suppressBottomBorder : true ,
327+ gapBelow : 10 ,
328+ } ;
329+ applyParagraphBorderStyles ( e , borders , info ) ;
330+ expect ( e . style . getPropertyValue ( 'border-top-style' ) ) . toBe ( '' ) ;
331+ expect ( e . style . getPropertyValue ( 'border-bottom-style' ) ) . toBe ( '' ) ;
332+ expect ( e . style . getPropertyValue ( 'border-left-style' ) ) . toBe ( 'solid' ) ;
333+ expect ( e . style . getPropertyValue ( 'border-right-style' ) ) . toBe ( 'solid' ) ;
334+ } ) ;
277335} ) ;
278336
279337// ---------------------------------------------------------------------------
@@ -283,15 +341,25 @@ describe('applyParagraphBorderStyles — between borders', () => {
283341describe ( 'createParagraphDecorationLayers — gap extension' , ( ) => {
284342 it ( 'sets bottom to negative gapBelow when showBetweenBorder is true' , ( ) => {
285343 const attrs = { borders : { top : { style : 'solid' as const , width : 1 } } , shading : { fill : '#EEE' } } ;
286- const info : BetweenBorderInfo = { showBetweenBorder : true , suppressTopBorder : false , gapBelow : 8 } ;
344+ const info : BetweenBorderInfo = {
345+ showBetweenBorder : true ,
346+ suppressTopBorder : false ,
347+ suppressBottomBorder : false ,
348+ gapBelow : 8 ,
349+ } ;
287350 const { borderLayer, shadingLayer } = createParagraphDecorationLayers ( document , 400 , attrs , info ) ;
288351 expect ( borderLayer ! . style . bottom ) . toBe ( '-8px' ) ;
289352 expect ( shadingLayer ! . style . bottom ) . toBe ( '-8px' ) ;
290353 } ) ;
291354
292355 it ( 'sets bottom to 0px when gapBelow is 0' , ( ) => {
293356 const attrs = { borders : { top : { style : 'solid' as const , width : 1 } } } ;
294- const info : BetweenBorderInfo = { showBetweenBorder : true , suppressTopBorder : false , gapBelow : 0 } ;
357+ const info : BetweenBorderInfo = {
358+ showBetweenBorder : true ,
359+ suppressTopBorder : false ,
360+ suppressBottomBorder : false ,
361+ gapBelow : 0 ,
362+ } ;
295363 const { borderLayer } = createParagraphDecorationLayers ( document , 400 , attrs , info ) ;
296364 expect ( borderLayer ! . style . bottom ) . toBe ( '0px' ) ;
297365 } ) ;
@@ -302,12 +370,31 @@ describe('createParagraphDecorationLayers — gap extension', () => {
302370 expect ( borderLayer ! . style . bottom ) . toBe ( '0px' ) ;
303371 } ) ;
304372
305- it ( 'sets bottom to 0px when showBetweenBorder is false even with gapBelow ' , ( ) => {
373+ it ( 'sets bottom to 0px when showBetweenBorder is false and suppressBottomBorder is false ' , ( ) => {
306374 const attrs = { borders : { top : { style : 'solid' as const , width : 1 } } } ;
307- const info : BetweenBorderInfo = { showBetweenBorder : false , suppressTopBorder : true , gapBelow : 12 } ;
375+ const info : BetweenBorderInfo = {
376+ showBetweenBorder : false ,
377+ suppressTopBorder : true ,
378+ suppressBottomBorder : false ,
379+ gapBelow : 12 ,
380+ } ;
308381 const { borderLayer } = createParagraphDecorationLayers ( document , 400 , attrs , info ) ;
309382 expect ( borderLayer ! . style . bottom ) . toBe ( '0px' ) ;
310383 } ) ;
384+
385+ it ( 'sets bottom to negative gapBelow when suppressBottomBorder is true (nil/none between group)' , ( ) => {
386+ const attrs = {
387+ borders : { top : { style : 'solid' as const , width : 1 } , bottom : { style : 'solid' as const , width : 1 } } ,
388+ } ;
389+ const info : BetweenBorderInfo = {
390+ showBetweenBorder : false ,
391+ suppressTopBorder : false ,
392+ suppressBottomBorder : true ,
393+ gapBelow : 10 ,
394+ } ;
395+ const { borderLayer } = createParagraphDecorationLayers ( document , 400 , attrs , info ) ;
396+ expect ( borderLayer ! . style . bottom ) . toBe ( '-10px' ) ;
397+ } ) ;
311398} ) ;
312399
313400// ---------------------------------------------------------------------------
@@ -630,6 +717,78 @@ describe('computeBetweenBorderFlags', () => {
630717 const flags = computeBetweenBorderFlags ( fragments , lookup ) ;
631718 expect ( flags . size ) . toBe ( 2 ) ;
632719 } ) ;
720+
721+ // --- nil/none between grouping (continuous box without separator) ---
722+ it ( 'groups paragraphs with between: {style: "none"} (nil/none between)' , ( ) => {
723+ const borders : ParagraphBorders = {
724+ top : { style : 'solid' , width : 1 , color : '#000' } ,
725+ right : { style : 'solid' , width : 1 , color : '#000' } ,
726+ bottom : { style : 'solid' , width : 1 , color : '#000' } ,
727+ left : { style : 'solid' , width : 1 , color : '#000' } ,
728+ between : { style : 'none' } ,
729+ } ;
730+ const b1 = makeParagraphBlock ( 'b1' , borders ) ;
731+ const b2 = makeParagraphBlock ( 'b2' , borders ) ;
732+ const lookup = buildLookup ( [ { block : b1 } , { block : b2 } ] ) ;
733+ const fragments : Fragment [ ] = [ paraFragment ( 'b1' , { y : 0 } ) , paraFragment ( 'b2' , { y : 20 } ) ] ;
734+
735+ const flags = computeBetweenBorderFlags ( fragments , lookup ) ;
736+ expect ( flags . size ) . toBe ( 2 ) ;
737+ // First fragment: suppressBottomBorder (not showBetweenBorder)
738+ expect ( flags . get ( 0 ) ?. showBetweenBorder ) . toBe ( false ) ;
739+ expect ( flags . get ( 0 ) ?. suppressBottomBorder ) . toBe ( true ) ;
740+ // Second fragment: suppressTopBorder
741+ expect ( flags . get ( 1 ) ?. suppressTopBorder ) . toBe ( true ) ;
742+ expect ( flags . get ( 1 ) ?. suppressBottomBorder ) . toBe ( false ) ;
743+ } ) ;
744+
745+ it ( 'groups chain of 3 paragraphs with nil/none between' , ( ) => {
746+ const borders : ParagraphBorders = {
747+ top : { style : 'solid' , width : 1 , color : '#000' } ,
748+ bottom : { style : 'solid' , width : 1 , color : '#000' } ,
749+ left : { style : 'solid' , width : 1 , color : '#000' } ,
750+ right : { style : 'solid' , width : 1 , color : '#000' } ,
751+ between : { style : 'none' } ,
752+ } ;
753+ const b1 = makeParagraphBlock ( 'b1' , borders ) ;
754+ const b2 = makeParagraphBlock ( 'b2' , borders ) ;
755+ const b3 = makeParagraphBlock ( 'b3' , borders ) ;
756+ const lookup = buildLookup ( [ { block : b1 } , { block : b2 } , { block : b3 } ] ) ;
757+ const fragments : Fragment [ ] = [
758+ paraFragment ( 'b1' , { y : 0 } ) ,
759+ paraFragment ( 'b2' , { y : 20 } ) ,
760+ paraFragment ( 'b3' , { y : 40 } ) ,
761+ ] ;
762+
763+ const flags = computeBetweenBorderFlags ( fragments , lookup ) ;
764+ expect ( flags . size ) . toBe ( 3 ) ;
765+ // First: suppress bottom, keep top
766+ expect ( flags . get ( 0 ) ?. suppressBottomBorder ) . toBe ( true ) ;
767+ expect ( flags . get ( 0 ) ?. suppressTopBorder ) . toBe ( false ) ;
768+ // Middle: suppress both top and bottom
769+ expect ( flags . get ( 1 ) ?. suppressTopBorder ) . toBe ( true ) ;
770+ expect ( flags . get ( 1 ) ?. suppressBottomBorder ) . toBe ( true ) ;
771+ // Last: suppress top, keep bottom
772+ expect ( flags . get ( 2 ) ?. suppressTopBorder ) . toBe ( true ) ;
773+ expect ( flags . get ( 2 ) ?. suppressBottomBorder ) . toBe ( false ) ;
774+ } ) ;
775+
776+ it ( 'does not group nil/none between with real between (different hashes)' , ( ) => {
777+ const nilBetween : ParagraphBorders = {
778+ top : { style : 'solid' , width : 1 , color : '#000' } ,
779+ between : { style : 'none' } ,
780+ } ;
781+ const realBetween : ParagraphBorders = {
782+ top : { style : 'solid' , width : 1 , color : '#000' } ,
783+ between : { style : 'solid' , width : 1 , color : '#000' } ,
784+ } ;
785+ const b1 = makeParagraphBlock ( 'b1' , nilBetween ) ;
786+ const b2 = makeParagraphBlock ( 'b2' , realBetween ) ;
787+ const lookup = buildLookup ( [ { block : b1 } , { block : b2 } ] ) ;
788+ const fragments : Fragment [ ] = [ paraFragment ( 'b1' ) , paraFragment ( 'b2' ) ] ;
789+
790+ expect ( computeBetweenBorderFlags ( fragments , lookup ) . size ) . toBe ( 0 ) ;
791+ } ) ;
633792} ) ;
634793
635794// ---------------------------------------------------------------------------
0 commit comments