1- import { close , grow , open , rgba , text } from "../ops.ts" ;
1+ import { close , fixed , grow , open , rgba , text } from "../ops.ts" ;
22import { createTerm } from "../term.ts" ;
33import { describe , expect , it } from "./suite.ts" ;
44
55const decode = ( b : Uint8Array ) => new TextDecoder ( ) . decode ( b ) ;
66
7- type TextBgColor = {
7+ type BgColor = {
88 value : number ;
99 sgr : string ;
1010} ;
1111
12- function randomTextBgColor ( ) : TextBgColor {
12+ type Cell = {
13+ ch : string ;
14+ bg ?: string ;
15+ } ;
16+
17+ function randomBgColor ( ) : BgColor {
1318 let r = 0 ;
1419 let g = 0 ;
1520 let b = 0 ;
@@ -30,6 +35,41 @@ function randomTextBgColor(): TextBgColor {
3035 } ;
3136}
3237
38+ function cells ( ansi : string ) : Cell [ ] {
39+ let result : Cell [ ] = [ ] ;
40+ let bg : string | undefined ;
41+
42+ for ( let i = 0 ; i < ansi . length ; ) {
43+ if ( ansi [ i ] === "\x1b" && ansi [ i + 1 ] === "[" ) {
44+ let end = i + 2 ;
45+ while ( end < ansi . length && ! / [ A - Z a - z ] / . test ( ansi [ end ] ) ) {
46+ end ++ ;
47+ }
48+
49+ let seq = ansi . slice ( i , end + 1 ) ;
50+ if ( seq === "\x1b[0m" ) {
51+ bg = undefined ;
52+ } else if ( seq . startsWith ( "\x1b[48;2;" ) && seq . endsWith ( "m" ) ) {
53+ bg = seq . slice ( 0 , - 1 ) ;
54+ }
55+
56+ i = end + 1 ;
57+ continue ;
58+ }
59+
60+ result . push ( { ch : ansi [ i ] , bg } ) ;
61+ i ++ ;
62+ }
63+
64+ return result ;
65+ }
66+
67+ function firstCell ( cells : Cell [ ] , ch : string ) : Cell {
68+ let cell = cells . find ( ( c ) => c . ch === ch ) ;
69+ expect ( cell ) . toBeDefined ( ) ;
70+ return cell ! ;
71+ }
72+
3373describe ( "foreground" , ( ) => {
3474 it ( "emits uncolored text with no foreground" , async ( ) => {
3575 let term = await createTerm ( { width : 12 , height : 1 } ) ;
@@ -41,9 +81,67 @@ describe("foreground", () => {
4181} ) ;
4282
4383describe ( "background" , ( ) => {
84+ it ( "fills border cells with the requested border-level bg" , async ( ) => {
85+ let term = await createTerm ( { width : 12 , height : 4 } ) ;
86+ let bg = randomBgColor ( ) ;
87+ let ansi = decode (
88+ term . render ( [
89+ open ( "box" , {
90+ layout : { width : fixed ( 8 ) , height : fixed ( 3 ) , direction : "ttb" } ,
91+ border : {
92+ color : rgba ( 255 , 255 , 255 ) ,
93+ bg : bg . value ,
94+ left : 1 ,
95+ right : 1 ,
96+ top : 1 ,
97+ bottom : 1 ,
98+ } ,
99+ } ) ,
100+ text ( "Hi" ) ,
101+ close ( ) ,
102+ ] , { mode : "line" } ) . output ,
103+ ) ;
104+
105+ expect ( ansi ) . toContain ( `${ bg . sgr } m┌` ) ;
106+
107+ let rendered = cells ( ansi ) ;
108+ expect ( firstCell ( rendered , "┌" ) . bg ) . toBe ( bg . sgr ) ;
109+ expect ( firstCell ( rendered , "─" ) . bg ) . toBe ( bg . sgr ) ;
110+ expect ( firstCell ( rendered , "┐" ) . bg ) . toBe ( bg . sgr ) ;
111+ expect ( firstCell ( rendered , "│" ) . bg ) . toBe ( bg . sgr ) ;
112+ } ) ;
113+
114+ it ( "leaves existing border-cell bg unchanged when border bg is omitted" , async ( ) => {
115+ let term = await createTerm ( { width : 12 , height : 4 } ) ;
116+ let bg = randomBgColor ( ) ;
117+ let ansi = decode (
118+ term . render ( [
119+ open ( "box" , {
120+ layout : { width : fixed ( 8 ) , height : fixed ( 3 ) , direction : "ttb" } ,
121+ bg : bg . value ,
122+ border : {
123+ color : rgba ( 255 , 255 , 255 ) ,
124+ left : 1 ,
125+ right : 1 ,
126+ top : 1 ,
127+ bottom : 1 ,
128+ } ,
129+ } ) ,
130+ text ( "Hi" ) ,
131+ close ( ) ,
132+ ] , { mode : "line" } ) . output ,
133+ ) ;
134+
135+ let rendered = cells ( ansi ) ;
136+ expect ( firstCell ( rendered , "┌" ) . bg ) . toBe ( bg . sgr ) ;
137+ expect ( firstCell ( rendered , "─" ) . bg ) . toBe ( bg . sgr ) ;
138+ expect ( firstCell ( rendered , "┐" ) . bg ) . toBe ( bg . sgr ) ;
139+ expect ( firstCell ( rendered , "│" ) . bg ) . toBe ( bg . sgr ) ;
140+ } ) ;
141+
44142 it ( "fills glyph cells with the requested text-level bg" , async ( ) => {
45143 let term = await createTerm ( { width : 20 , height : 1 } ) ;
46- let bg = randomTextBgColor ( ) ;
144+ let bg = randomBgColor ( ) ;
47145 let ansi = decode (
48146 term . render ( [
49147 open ( "root" , { layout : { width : grow ( ) , height : grow ( ) } } ) ,
@@ -56,9 +154,52 @@ describe("background", () => {
56154 expect ( beforeH ) . toContain ( bg . sgr ) ;
57155 } ) ;
58156
157+ it ( "resets border bg on subsequent frames without border bg" , async ( ) => {
158+ let term = await createTerm ( { width : 12 , height : 4 } ) ;
159+ let bg = randomBgColor ( ) ;
160+
161+ // Frame 1: border with bg
162+ term . render ( [
163+ open ( "box" , {
164+ layout : { width : fixed ( 8 ) , height : fixed ( 3 ) , direction : "ttb" } ,
165+ border : {
166+ color : rgba ( 255 , 255 , 255 ) ,
167+ bg : bg . value ,
168+ left : 1 ,
169+ right : 1 ,
170+ top : 1 ,
171+ bottom : 1 ,
172+ } ,
173+ } ) ,
174+ text ( "Hi" ) ,
175+ close ( ) ,
176+ ] ) ;
177+
178+ // Frame 2: same border, no bg
179+ let ansi = decode (
180+ term . render ( [
181+ open ( "box" , {
182+ layout : { width : fixed ( 8 ) , height : fixed ( 3 ) , direction : "ttb" } ,
183+ border : {
184+ color : rgba ( 255 , 255 , 255 ) ,
185+ left : 1 ,
186+ right : 1 ,
187+ top : 1 ,
188+ bottom : 1 ,
189+ } ,
190+ } ) ,
191+ text ( "Hi" ) ,
192+ close ( ) ,
193+ ] ) . output ,
194+ ) ;
195+
196+ expect ( ansi ) . not . toContain ( bg . sgr ) ;
197+ expect ( firstCell ( cells ( ansi ) , "┌" ) . bg ) . toBeUndefined ( ) ;
198+ } ) ;
199+
59200 it ( "resets the background before writing trailing cells" , async ( ) => {
60201 let term = await createTerm ( { width : 20 , height : 1 } ) ;
61- let bg = randomTextBgColor ( ) ;
202+ let bg = randomBgColor ( ) ;
62203 let ansi = decode (
63204 term . render ( [
64205 open ( "root" , { layout : { width : grow ( ) , height : grow ( ) } } ) ,
0 commit comments