@@ -231,6 +231,77 @@ describe('length bound variables (padding / gap / size / radius)', () => {
231231 borderRadius : '$radiusTl $radiusRl 12px' ,
232232 } )
233233 } )
234+
235+ test ( 'getBorderRadiusProps uses two-value shorthand when tl===br and tr===bl' , async ( ) => {
236+ setupFigmaMocks ( {
237+ variableNamesById : {
238+ 'var-a' : 'radiusA' ,
239+ 'var-b' : 'radiusB' ,
240+ } ,
241+ } )
242+
243+ const node = {
244+ type : 'RECTANGLE' ,
245+ cornerRadius : 8 ,
246+ topLeftRadius : 8 ,
247+ topRightRadius : 4 ,
248+ bottomRightRadius : 8 ,
249+ bottomLeftRadius : 4 ,
250+ boundVariables : {
251+ topLeftRadius : { id : 'var-a' } ,
252+ topRightRadius : { id : 'var-b' } ,
253+ bottomRightRadius : { id : 'var-a' } ,
254+ bottomLeftRadius : { id : 'var-b' } ,
255+ } ,
256+ } as unknown as SceneNode
257+
258+ expect ( await getBorderRadiusProps ( node ) ) . toEqual ( {
259+ borderRadius : '$radiusA $radiusB' ,
260+ } )
261+ } )
262+
263+ test ( 'getBorderRadiusProps uses four-value when all corners differ' , async ( ) => {
264+ setupFigmaMocks ( {
265+ variableNamesById : {
266+ 'var-a' : 'a' ,
267+ 'var-b' : 'b' ,
268+ 'var-c' : 'c' ,
269+ 'var-d' : 'd' ,
270+ } ,
271+ } )
272+
273+ const node = {
274+ type : 'RECTANGLE' ,
275+ cornerRadius : 8 ,
276+ topLeftRadius : 1 ,
277+ topRightRadius : 2 ,
278+ bottomRightRadius : 3 ,
279+ bottomLeftRadius : 4 ,
280+ boundVariables : {
281+ topLeftRadius : { id : 'var-a' } ,
282+ topRightRadius : { id : 'var-b' } ,
283+ bottomRightRadius : { id : 'var-c' } ,
284+ bottomLeftRadius : { id : 'var-d' } ,
285+ } ,
286+ } as unknown as SceneNode
287+
288+ expect ( await getBorderRadiusProps ( node ) ) . toEqual ( {
289+ borderRadius : '$a $b $c $d' ,
290+ } )
291+ } )
292+
293+ test ( 'getBorderRadiusProps falls back to cornerRadius when corner fields are unavailable' , async ( ) => {
294+ setupFigmaMocks ( )
295+
296+ const node = {
297+ type : 'VECTOR' ,
298+ cornerRadius : 10 ,
299+ } as unknown as SceneNode
300+
301+ expect ( await getBorderRadiusProps ( node ) ) . toEqual ( {
302+ borderRadius : '10px' ,
303+ } )
304+ } )
234305} )
235306
236307describe ( 'effect/text-shadow bound variables and style tokens' , ( ) => {
@@ -281,6 +352,31 @@ describe('effect/text-shadow bound variables and style tokens', () => {
281352 } )
282353 } )
283354
355+ test ( 'getEffectProps does not set __boxShadowToken when style has no name' , async ( ) => {
356+ setupFigmaMocks ( {
357+ styleNamesById : { } , // style lookup returns null
358+ } )
359+
360+ const node = {
361+ type : 'FRAME' ,
362+ effectStyleId : 'style-no-name' ,
363+ effects : [
364+ {
365+ type : 'DROP_SHADOW' ,
366+ visible : true ,
367+ offset : { x : 0 , y : 4 } ,
368+ radius : 8 ,
369+ spread : 0 ,
370+ color : { r : 0 , g : 0 , b : 0 , a : 1 } ,
371+ } ,
372+ ] ,
373+ } as unknown as SceneNode
374+
375+ const result = await getEffectProps ( node )
376+ expect ( result ?. boxShadow ) . toBe ( '0 4px 8px 0 #000' )
377+ expect ( result ?. __boxShadowToken ) . toBeUndefined ( )
378+ } )
379+
284380 test ( 'getEffectProps does not set __boxShadowToken when effectStyleId is empty' , async ( ) => {
285381 setupFigmaMocks ( {
286382 styleNamesById : {
@@ -308,6 +404,35 @@ describe('effect/text-shadow bound variables and style tokens', () => {
308404 expect ( result ?. __boxShadowToken ) . toBeUndefined ( )
309405 } )
310406
407+ test ( 'getEffectProps falls back to raw values when bound variable ids are unresolved' , async ( ) => {
408+ setupFigmaMocks ( )
409+
410+ const node = {
411+ type : 'FRAME' ,
412+ effects : [
413+ {
414+ type : 'DROP_SHADOW' ,
415+ visible : true ,
416+ offset : { x : 5 , y : 7 } ,
417+ radius : 9 ,
418+ spread : 11 ,
419+ color : { r : 0.1 , g : 0.2 , b : 0.3 , a : 1 } ,
420+ boundVariables : {
421+ offsetX : { id : 'unknown-x' } ,
422+ offsetY : { id : 'unknown-y' } ,
423+ radius : { id : 'unknown-r' } ,
424+ spread : { id : 'unknown-s' } ,
425+ color : { id : 'unknown-c' } ,
426+ } ,
427+ } ,
428+ ] ,
429+ } as unknown as SceneNode
430+
431+ expect ( await getEffectProps ( node ) ) . toEqual ( {
432+ boxShadow : '5px 7px 9px 11px #1A334D' ,
433+ } )
434+ } )
435+
311436 test ( 'getTextShadowProps resolves effect style token and bound variables' , async ( ) => {
312437 setupFigmaMocks ( {
313438 variableNamesById : {
@@ -348,6 +473,30 @@ describe('effect/text-shadow bound variables and style tokens', () => {
348473 } )
349474 } )
350475
476+ test ( 'getTextShadowProps does not set __textShadowToken when style has no name' , async ( ) => {
477+ setupFigmaMocks ( {
478+ styleNamesById : { } ,
479+ } )
480+
481+ const node = {
482+ type : 'TEXT' ,
483+ effectStyleId : 'style-no-name' ,
484+ effects : [
485+ {
486+ type : 'DROP_SHADOW' ,
487+ visible : true ,
488+ offset : { x : 1 , y : 2 } ,
489+ radius : 3 ,
490+ color : { r : 0 , g : 0 , b : 0 , a : 1 } ,
491+ } ,
492+ ] ,
493+ } as unknown as TextNode
494+
495+ const result = await getTextShadowProps ( node )
496+ expect ( result ?. textShadow ) . toBe ( '1px 2px 3px #000' )
497+ expect ( result ?. __textShadowToken ) . toBeUndefined ( )
498+ } )
499+
351500 test ( 'getTextShadowProps does not set __textShadowToken when effectStyleId is empty' , async ( ) => {
352501 setupFigmaMocks ( )
353502
@@ -369,4 +518,31 @@ describe('effect/text-shadow bound variables and style tokens', () => {
369518 expect ( result ?. textShadow ) . toBe ( '2px 4px 6px #000' )
370519 expect ( result ?. __textShadowToken ) . toBeUndefined ( )
371520 } )
521+
522+ test ( 'getTextShadowProps falls back to raw values when bound variable ids are unresolved' , async ( ) => {
523+ setupFigmaMocks ( )
524+
525+ const node = {
526+ type : 'TEXT' ,
527+ effects : [
528+ {
529+ type : 'DROP_SHADOW' ,
530+ visible : true ,
531+ offset : { x : 3 , y : 6 } ,
532+ radius : 9 ,
533+ color : { r : 0.5 , g : 0.25 , b : 0.75 , a : 1 } ,
534+ boundVariables : {
535+ offsetX : { id : 'unknown-x' } ,
536+ offsetY : { id : 'unknown-y' } ,
537+ radius : { id : 'unknown-r' } ,
538+ color : { id : 'unknown-c' } ,
539+ } ,
540+ } ,
541+ ] ,
542+ } as unknown as TextNode
543+
544+ expect ( await getTextShadowProps ( node ) ) . toEqual ( {
545+ textShadow : '3px 6px 9px #8040BF' ,
546+ } )
547+ } )
372548} )
0 commit comments