@@ -221,5 +221,75 @@ public async Task ShouldRenderTrailingQuestionWithFilters()
221221 var result = await template . RenderAsync ( context ) ;
222222 Assert . Equal ( "HELLO" , result ) ;
223223 }
224+
225+ [ Fact ]
226+ public void ShouldSupportTrailingQuestionOnIntermediateIdentifiers ( )
227+ {
228+ var parser = new FluidParser ( new FluidParserOptions { AllowTrailingQuestion = true } ) ;
229+ parser . TryParse ( "{{ a?.b.c }}" , out var template , out var errors ) ;
230+
231+ var statements = ( ( FluidTemplate ) template ) . Statements ;
232+ var outputStatement = statements [ 0 ] as OutputStatement ;
233+ Assert . NotNull ( outputStatement ) ;
234+
235+ var memberExpression = outputStatement . Expression as MemberExpression ;
236+ Assert . NotNull ( memberExpression ) ;
237+ Assert . Equal ( 3 , memberExpression . Segments . Count ) ;
238+
239+ var firstSegment = memberExpression . Segments [ 0 ] as IdentifierSegment ;
240+ Assert . NotNull ( firstSegment ) ;
241+ Assert . Equal ( "a" , firstSegment . Identifier ) ; // Should NOT contain '?'
242+
243+ var secondSegment = memberExpression . Segments [ 1 ] as IdentifierSegment ;
244+ Assert . NotNull ( secondSegment ) ;
245+ Assert . Equal ( "b" , secondSegment . Identifier ) ;
246+
247+ var thirdSegment = memberExpression . Segments [ 2 ] as IdentifierSegment ;
248+ Assert . NotNull ( thirdSegment ) ;
249+ Assert . Equal ( "c" , thirdSegment . Identifier ) ;
250+ }
251+
252+ [ Fact ]
253+ public async Task ShouldResolveIntermediateIdentifiersWithTrailingQuestion ( )
254+ {
255+ var parser = new FluidParser ( new FluidParserOptions { AllowTrailingQuestion = true } ) ;
256+ parser . TryParse ( "{{ obj?.nested.value }}" , out var template , out var errors ) ;
257+
258+ var context = new TemplateContext ( ) ;
259+ var nested = new { value = "test" } ;
260+ var obj = new { nested } ;
261+ context . Options . MemberAccessStrategy . Register ( nested . GetType ( ) ) ;
262+ context . Options . MemberAccessStrategy . Register ( obj . GetType ( ) ) ;
263+ context . SetValue ( "obj" , obj ) ;
264+
265+ var result = await template . RenderAsync ( context ) ;
266+ Assert . Equal ( "test" , result ) ;
267+ }
268+
269+ [ Theory ]
270+ [ InlineData ( "{{ a?.b.c }}" , new [ ] { "a" , "b" , "c" } ) ]
271+ [ InlineData ( "{{ a.b?.c }}" , new [ ] { "a" , "b" , "c" } ) ]
272+ [ InlineData ( "{{ a?.b?.c }}" , new [ ] { "a" , "b" , "c" } ) ]
273+ [ InlineData ( "{{ a?.b?.c? }}" , new [ ] { "a" , "b" , "c" } ) ]
274+ public void ShouldStripTrailingQuestionFromAllSegments ( string template , string [ ] expectedIdentifiers )
275+ {
276+ var parser = new FluidParser ( new FluidParserOptions { AllowTrailingQuestion = true } ) ;
277+ parser . TryParse ( template , out var parsedTemplate , out var errors ) ;
278+
279+ var statements = ( ( FluidTemplate ) parsedTemplate ) . Statements ;
280+ var outputStatement = statements [ 0 ] as OutputStatement ;
281+ Assert . NotNull ( outputStatement ) ;
282+
283+ var memberExpression = outputStatement . Expression as MemberExpression ;
284+ Assert . NotNull ( memberExpression ) ;
285+ Assert . Equal ( expectedIdentifiers . Length , memberExpression . Segments . Count ) ;
286+
287+ for ( int i = 0 ; i < expectedIdentifiers . Length ; i ++ )
288+ {
289+ var segment = memberExpression . Segments [ i ] as IdentifierSegment ;
290+ Assert . NotNull ( segment ) ;
291+ Assert . Equal ( expectedIdentifiers [ i ] , segment . Identifier ) ;
292+ }
293+ }
224294 }
225295}
0 commit comments