@@ -355,3 +355,346 @@ describe('Codegen viewport variant', () => {
355355 expect ( codes . length ) . toBe ( 2 )
356356 } )
357357} )
358+
359+ describe ( 'Codegen effect-only COMPONENT_SET' , ( ) => {
360+ test ( 'generates code with pseudo-selectors for effect-only component set' , async ( ) => {
361+ // Create effect variants: default, hover, active, disabled
362+ const defaultVariant = createComponentNode (
363+ 'effect=default' ,
364+ { effect : 'default' } ,
365+ {
366+ fills : [
367+ {
368+ type : 'SOLID' ,
369+ visible : true ,
370+ color : { r : 0.5 , g : 0.3 , b : 0.9 } ,
371+ opacity : 1 ,
372+ } as unknown as Paint ,
373+ ] ,
374+ reactions : [
375+ {
376+ trigger : { type : 'ON_HOVER' } ,
377+ actions : [
378+ {
379+ type : 'NODE' ,
380+ transition : {
381+ type : 'SMART_ANIMATE' ,
382+ duration : 0.3 ,
383+ easing : { type : 'EASE_OUT' } ,
384+ } ,
385+ } ,
386+ ] ,
387+ } ,
388+ ] as unknown as Reaction [ ] ,
389+ } ,
390+ )
391+
392+ const hoverVariant = createComponentNode (
393+ 'effect=hover' ,
394+ { effect : 'hover' } ,
395+ {
396+ fills : [
397+ {
398+ type : 'SOLID' ,
399+ visible : true ,
400+ color : { r : 0.4 , g : 0.2 , b : 0.8 } ,
401+ opacity : 1 ,
402+ } as unknown as Paint ,
403+ ] ,
404+ } ,
405+ )
406+
407+ const activeVariant = createComponentNode (
408+ 'effect=active' ,
409+ { effect : 'active' } ,
410+ {
411+ fills : [
412+ {
413+ type : 'SOLID' ,
414+ visible : true ,
415+ color : { r : 0.3 , g : 0.1 , b : 0.7 } ,
416+ opacity : 1 ,
417+ } as unknown as Paint ,
418+ ] ,
419+ } ,
420+ )
421+
422+ const disabledVariant = createComponentNode (
423+ 'effect=disabled' ,
424+ { effect : 'disabled' } ,
425+ {
426+ fills : [
427+ {
428+ type : 'SOLID' ,
429+ visible : true ,
430+ color : { r : 0.8 , g : 0.8 , b : 0.8 } ,
431+ opacity : 1 ,
432+ } as unknown as Paint ,
433+ ] ,
434+ } ,
435+ )
436+
437+ const componentSet = createComponentSetNode (
438+ 'EffectButton' ,
439+ {
440+ effect : {
441+ type : 'VARIANT' ,
442+ defaultValue : 'default' ,
443+ variantOptions : [ 'default' , 'hover' , 'active' , 'disabled' ] ,
444+ } ,
445+ } ,
446+ [ defaultVariant , hoverVariant , activeVariant , disabledVariant ] ,
447+ )
448+
449+ const codes = await ResponsiveCodegen . generateVariantResponsiveComponents (
450+ componentSet ,
451+ 'EffectButton' ,
452+ )
453+
454+ expect ( codes . length ) . toBe ( 1 )
455+ const [ componentName , generatedCode ] = codes [ 0 ]
456+ expect ( componentName ) . toBe ( 'EffectButton' )
457+
458+ // Should have pseudo-selector props
459+ expect ( generatedCode ) . toContain ( '_hover' )
460+ expect ( generatedCode ) . toContain ( '_active' )
461+ expect ( generatedCode ) . toContain ( '_disabled' )
462+
463+ // Should have transition properties
464+ expect ( generatedCode ) . toContain ( 'transition=' )
465+ expect ( generatedCode ) . toContain ( 'transitionProperty=' )
466+
467+ // Should NOT have effect as a prop (handled via pseudo-selectors)
468+ expect ( generatedCode ) . not . toContain ( 'effect:' )
469+ } )
470+
471+ test ( 'generates code with viewport + effect variants' , async ( ) => {
472+ // Mobile variants
473+ const mobileDefault = createComponentNode (
474+ 'effect=default, viewport=Mobile' ,
475+ { effect : 'default' , viewport : 'Mobile' } ,
476+ {
477+ width : 150 ,
478+ height : 50 ,
479+ layoutSizingHorizontal : 'FIXED' ,
480+ layoutSizingVertical : 'FIXED' ,
481+ fills : [
482+ {
483+ type : 'SOLID' ,
484+ visible : true ,
485+ color : { r : 0.5 , g : 0.5 , b : 0.5 } ,
486+ opacity : 1 ,
487+ } as unknown as Paint ,
488+ ] ,
489+ } ,
490+ )
491+
492+ const mobileHover = createComponentNode (
493+ 'effect=hover, viewport=Mobile' ,
494+ { effect : 'hover' , viewport : 'Mobile' } ,
495+ {
496+ width : 150 ,
497+ height : 50 ,
498+ layoutSizingHorizontal : 'FIXED' ,
499+ layoutSizingVertical : 'FIXED' ,
500+ fills : [
501+ {
502+ type : 'SOLID' ,
503+ visible : true ,
504+ color : { r : 0.6 , g : 0.6 , b : 0.6 } ,
505+ opacity : 1 ,
506+ } as unknown as Paint ,
507+ ] ,
508+ } ,
509+ )
510+
511+ // Desktop variants
512+ const desktopDefault = createComponentNode (
513+ 'effect=default, viewport=Desktop' ,
514+ { effect : 'default' , viewport : 'Desktop' } ,
515+ {
516+ width : 200 ,
517+ height : 50 ,
518+ layoutSizingHorizontal : 'FIXED' ,
519+ layoutSizingVertical : 'FIXED' ,
520+ fills : [
521+ {
522+ type : 'SOLID' ,
523+ visible : true ,
524+ color : { r : 0.5 , g : 0.5 , b : 0.5 } ,
525+ opacity : 1 ,
526+ } as unknown as Paint ,
527+ ] ,
528+ } ,
529+ )
530+
531+ const desktopHover = createComponentNode (
532+ 'effect=hover, viewport=Desktop' ,
533+ { effect : 'hover' , viewport : 'Desktop' } ,
534+ {
535+ width : 200 ,
536+ height : 50 ,
537+ layoutSizingHorizontal : 'FIXED' ,
538+ layoutSizingVertical : 'FIXED' ,
539+ fills : [
540+ {
541+ type : 'SOLID' ,
542+ visible : true ,
543+ color : { r : 0.7 , g : 0.7 , b : 0.7 } ,
544+ opacity : 1 ,
545+ } as unknown as Paint ,
546+ ] ,
547+ } ,
548+ )
549+
550+ const componentSet = createComponentSetNode (
551+ 'ResponsiveEffectButton' ,
552+ {
553+ effect : {
554+ type : 'VARIANT' ,
555+ defaultValue : 'default' ,
556+ variantOptions : [ 'default' , 'hover' ] ,
557+ } ,
558+ viewport : {
559+ type : 'VARIANT' ,
560+ defaultValue : 'Desktop' ,
561+ variantOptions : [ 'Mobile' , 'Desktop' ] ,
562+ } ,
563+ } ,
564+ [ mobileDefault , mobileHover , desktopDefault , desktopHover ] ,
565+ )
566+
567+ const codes = await ResponsiveCodegen . generateVariantResponsiveComponents (
568+ componentSet ,
569+ 'ResponsiveEffectButton' ,
570+ )
571+
572+ expect ( codes . length ) . toBe ( 1 )
573+ const [ componentName , generatedCode ] = codes [ 0 ]
574+ expect ( componentName ) . toBe ( 'ResponsiveEffectButton' )
575+
576+ // Should have responsive width (different for mobile vs desktop)
577+ expect ( generatedCode ) . toContain ( 'w={' )
578+ expect ( generatedCode ) . toContain ( '"150px"' )
579+ expect ( generatedCode ) . toContain ( '"200px"' )
580+
581+ // Should have _hover with responsive bg colors
582+ expect ( generatedCode ) . toContain ( '_hover' )
583+ } )
584+
585+ test ( 'generates code with effect + size variants' , async ( ) => {
586+ // Size=Md variants
587+ const mdDefault = createComponentNode (
588+ 'effect=default, size=Md' ,
589+ { effect : 'default' , size : 'Md' } ,
590+ {
591+ width : 100 ,
592+ height : 50 ,
593+ layoutSizingHorizontal : 'FIXED' ,
594+ layoutSizingVertical : 'FIXED' ,
595+ fills : [
596+ {
597+ type : 'SOLID' ,
598+ visible : true ,
599+ color : { r : 0.2 , g : 0.4 , b : 0.8 } ,
600+ opacity : 1 ,
601+ } as unknown as Paint ,
602+ ] ,
603+ } ,
604+ )
605+
606+ const mdHover = createComponentNode (
607+ 'effect=hover, size=Md' ,
608+ { effect : 'hover' , size : 'Md' } ,
609+ {
610+ width : 100 ,
611+ height : 50 ,
612+ layoutSizingHorizontal : 'FIXED' ,
613+ layoutSizingVertical : 'FIXED' ,
614+ fills : [
615+ {
616+ type : 'SOLID' ,
617+ visible : true ,
618+ color : { r : 0.3 , g : 0.5 , b : 0.9 } ,
619+ opacity : 1 ,
620+ } as unknown as Paint ,
621+ ] ,
622+ } ,
623+ )
624+
625+ // Size=Sm variants
626+ const smDefault = createComponentNode (
627+ 'effect=default, size=Sm' ,
628+ { effect : 'default' , size : 'Sm' } ,
629+ {
630+ width : 80 ,
631+ height : 40 ,
632+ layoutSizingHorizontal : 'FIXED' ,
633+ layoutSizingVertical : 'FIXED' ,
634+ fills : [
635+ {
636+ type : 'SOLID' ,
637+ visible : true ,
638+ color : { r : 0.2 , g : 0.4 , b : 0.8 } ,
639+ opacity : 1 ,
640+ } as unknown as Paint ,
641+ ] ,
642+ } ,
643+ )
644+
645+ const smHover = createComponentNode (
646+ 'effect=hover, size=Sm' ,
647+ { effect : 'hover' , size : 'Sm' } ,
648+ {
649+ width : 80 ,
650+ height : 40 ,
651+ layoutSizingHorizontal : 'FIXED' ,
652+ layoutSizingVertical : 'FIXED' ,
653+ fills : [
654+ {
655+ type : 'SOLID' ,
656+ visible : true ,
657+ color : { r : 0.3 , g : 0.5 , b : 0.9 } ,
658+ opacity : 1 ,
659+ } as unknown as Paint ,
660+ ] ,
661+ } ,
662+ )
663+
664+ const componentSet = createComponentSetNode (
665+ 'SizeEffectButton' ,
666+ {
667+ effect : {
668+ type : 'VARIANT' ,
669+ defaultValue : 'default' ,
670+ variantOptions : [ 'default' , 'hover' ] ,
671+ } ,
672+ size : {
673+ type : 'VARIANT' ,
674+ defaultValue : 'Md' ,
675+ variantOptions : [ 'Md' , 'Sm' ] ,
676+ } ,
677+ } ,
678+ [ mdDefault , mdHover , smDefault , smHover ] ,
679+ )
680+
681+ const codes = await ResponsiveCodegen . generateVariantResponsiveComponents (
682+ componentSet ,
683+ 'SizeEffectButton' ,
684+ )
685+
686+ expect ( codes . length ) . toBe ( 1 )
687+ const [ componentName , generatedCode ] = codes [ 0 ]
688+ expect ( componentName ) . toBe ( 'SizeEffectButton' )
689+
690+ // Should have size prop in interface
691+ expect ( generatedCode ) . toContain ( "size: 'Md' | 'Sm'" )
692+
693+ // Should have variant-conditional width
694+ expect ( generatedCode ) . toContain ( 'w={' )
695+ expect ( generatedCode ) . toContain ( '[size]' )
696+
697+ // Should have _hover props
698+ expect ( generatedCode ) . toContain ( '_hover' )
699+ } )
700+ } )
0 commit comments