@@ -43,6 +43,8 @@ public static partial class CheckoutToggles
4343 [ Theory ]
4444 [ InlineData ( "public static class CheckoutToggles { [FeatureToggleRule(\" x\" )] private static bool IsEnabled(CheckoutContext context) => true; }" , "PKFT001" ) ]
4545 [ InlineData ( "public static partial class CheckoutToggles;" , "PKFT002" ) ]
46+ [ InlineData ( "public static partial class CheckoutToggles { [FeatureToggleRule(\" x\" )] private static bool IsEnabled() => true; }" , "PKFT003" ) ]
47+ [ InlineData ( "public static partial class CheckoutToggles { [FeatureToggleRule(\" x\" )] private static bool IsEnabled(string context) => true; }" , "PKFT003" ) ]
4648 [ InlineData ( "public static partial class CheckoutToggles { [FeatureToggleRule(\" x\" )] private static string IsEnabled(CheckoutContext context) => \" yes\" ; }" , "PKFT003" ) ]
4749 [ InlineData ( "public static partial class CheckoutToggles { [FeatureToggleRule(\" x\" )] private bool IsEnabled(CheckoutContext context) => true; }" , "PKFT003" ) ]
4850 public Task Generator_Reports_Invalid_Feature_Toggle_Declarations ( string declaration , string diagnosticId )
@@ -56,6 +58,118 @@ public sealed record CheckoutContext(string Tenant, decimal Total);
5658 ScenarioExpect . Contains ( result . Diagnostics , diagnostic => diagnostic . Id == diagnosticId ) )
5759 . AssertPassed ( ) ;
5860
61+ [ Scenario ( "Generator emits feature toggle defaults and host shapes" ) ]
62+ [ Fact ]
63+ public Task Generator_Emits_Feature_Toggle_Defaults_And_Host_Shapes ( )
64+ => Given ( "feature toggle declarations with default names and host shapes" , ( ) => Compile ( """
65+ using PatternKit.Generators.FeatureToggles;
66+ namespace Demo;
67+ public sealed record CheckoutContext(string Tenant, decimal Total);
68+
69+ [GenerateFeatureToggleSet(typeof(CheckoutContext))]
70+ internal abstract partial class AbstractToggles
71+ {
72+ [FeatureToggleRule("enabled")]
73+ private static bool IsEnabled(CheckoutContext context) => true;
74+ }
75+
76+ [GenerateFeatureToggleSet(typeof(CheckoutContext), SetName = "tenant\\\"toggles")]
77+ public sealed partial class SealedToggles
78+ {
79+ [FeatureToggleRule("beta", DefaultEnabled = true)]
80+ private static bool Beta(CheckoutContext context) => context.Tenant == "beta";
81+ }
82+
83+ [GenerateFeatureToggleSet(typeof(CheckoutContext))]
84+ internal partial struct StructToggles
85+ {
86+ [FeatureToggleRule("large-order")]
87+ private static bool LargeOrder(CheckoutContext context) => context.Total >= 500m;
88+ }
89+ """ ) )
90+ . Then ( "generated sources preserve host shape and configured defaults" , result =>
91+ {
92+ ScenarioExpect . Empty ( result . Diagnostics ) ;
93+ ScenarioExpect . Equal ( 3 , result . GeneratedSources . Count ) ;
94+
95+ var combined = string . Join ( "\n " , result . GeneratedSources ) ;
96+ ScenarioExpect . Contains ( "internal abstract partial class AbstractToggles" , combined ) ;
97+ ScenarioExpect . Contains ( "public sealed partial class SealedToggles" , combined ) ;
98+ ScenarioExpect . Contains ( "internal partial struct StructToggles" , combined ) ;
99+ ScenarioExpect . Contains ( "Create(\" feature-toggles\" )" , combined ) ;
100+ ScenarioExpect . Contains ( "Create(\" tenant\\ \\ \\ \" toggles\" )" , combined ) ;
101+ ScenarioExpect . Contains ( ".AddRule(\" enabled\" , false, IsEnabled)" , combined ) ;
102+ ScenarioExpect . Contains ( ".AddRule(\" beta\" , true, Beta)" , combined ) ;
103+ ScenarioExpect . True ( result . EmitSuccess , result . EmitDiagnostics ) ;
104+ } )
105+ . AssertPassed ( ) ;
106+
107+ [ Scenario ( "Generator emits nested feature toggle host wrappers" ) ]
108+ [ Fact ]
109+ public Task Generator_Emits_Nested_Feature_Toggle_Host_Wrappers ( )
110+ => Given ( "nested feature toggle declarations" , ( ) => Compile ( """
111+ using PatternKit.Generators.FeatureToggles;
112+ namespace Demo;
113+ public sealed record CheckoutContext(string Tenant, decimal Total);
114+
115+ public partial class ToggleContainer
116+ {
117+ private partial class PrivateHost
118+ {
119+ [GenerateFeatureToggleSet(typeof(CheckoutContext))]
120+ protected partial class ProtectedToggles
121+ {
122+ [FeatureToggleRule("protected")]
123+ private static bool Protected(CheckoutContext context) => true;
124+ }
125+
126+ [GenerateFeatureToggleSet(typeof(CheckoutContext))]
127+ private protected partial class PrivateProtectedToggles
128+ {
129+ [FeatureToggleRule("private-protected")]
130+ private static bool PrivateProtected(CheckoutContext context) => true;
131+ }
132+
133+ [GenerateFeatureToggleSet(typeof(CheckoutContext))]
134+ protected internal partial class ProtectedInternalToggles
135+ {
136+ [FeatureToggleRule("protected-internal")]
137+ private static bool ProtectedInternal(CheckoutContext context) => true;
138+ }
139+ }
140+ }
141+ """ ) )
142+ . Then ( "generated sources preserve containing partial type wrappers" , result =>
143+ {
144+ ScenarioExpect . Empty ( result . Diagnostics ) ;
145+ ScenarioExpect . Equal ( 3 , result . GeneratedSources . Count ) ;
146+
147+ var combined = string . Join ( "\n " , result . GeneratedSources ) ;
148+ ScenarioExpect . Contains ( "public partial class ToggleContainer" , combined ) ;
149+ ScenarioExpect . Contains ( "private partial class PrivateHost" , combined ) ;
150+ ScenarioExpect . Contains ( "protected partial class ProtectedToggles" , combined ) ;
151+ ScenarioExpect . Contains ( "private protected partial class PrivateProtectedToggles" , combined ) ;
152+ ScenarioExpect . Contains ( "protected internal partial class ProtectedInternalToggles" , combined ) ;
153+ ScenarioExpect . True ( result . EmitSuccess , result . EmitDiagnostics ) ;
154+ } )
155+ . AssertPassed ( ) ;
156+
157+ [ Scenario ( "Generator skips malformed feature toggle context type" ) ]
158+ [ Fact ]
159+ public Task Generator_Skips_Malformed_Feature_Toggle_Context_Type ( )
160+ => Given ( "a feature toggle declaration with a null context type" , ( ) => Compile ( """
161+ using PatternKit.Generators.FeatureToggles;
162+ [GenerateFeatureToggleSet(null!)]
163+ public static partial class CheckoutToggles
164+ {
165+ [FeatureToggleRule("x")]
166+ private static bool IsEnabled(string context) => true;
167+ }
168+ """ ) )
169+ . Then ( "no source is generated" , result =>
170+ ScenarioExpect . Empty ( result . GeneratedSources ) )
171+ . AssertPassed ( ) ;
172+
59173 private static GeneratorResult Compile ( string source )
60174 {
61175 var compilation = RoslynTestHelpers . CreateCompilation (
0 commit comments