Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ protected override void Write(StringRender renderer, SectionToken obj, Context c
else if (LambdaTypes.Contains(value.GetType()))
{
var sectionContent = obj.SectionContent.ToString();
var lambdaView = FindNearestObjectView(context);

switch (value)
{
Expand All @@ -73,13 +74,13 @@ protected override void Write(StringRender renderer, SectionToken obj, Context c
case Func<string, Func<string, Task<string>>, Task<object>> _:
throw new StubbleException("Async lambdas are not allowed in non-async template rendering");
case Func<dynamic, string, object> func:
value = func(context.View, sectionContent);
value = func(lambdaView, sectionContent);
break;
case Func<string, object> func:
value = func(sectionContent);
break;
case Func<dynamic, string, Func<string, string>, object> func:
value = func(context.View, sectionContent, RenderInContext(context, obj.Tags));
value = func(lambdaView, sectionContent, RenderInContext(context, obj.Tags));
break;
case Func<string, Func<string, string>, object> func:
value = func(sectionContent, RenderInContext(context, obj.Tags));
Expand Down Expand Up @@ -127,14 +128,15 @@ protected override async Task WriteAsync(StringRender renderer, SectionToken obj
else if (LambdaTypes.Contains(value.GetType()))
{
var sectionContent = obj.SectionContent.ToString();
var lambdaView = FindNearestObjectView(context);

switch (value)
{
case Func<dynamic, string, Task<object>> func:
value = await func(context.View, sectionContent).ConfigureAwait(false);
value = await func(lambdaView, sectionContent).ConfigureAwait(false);
break;
case Func<dynamic, string, object> func:
value = func(context.View, sectionContent);
value = func(lambdaView, sectionContent);
break;
case Func<string, Task<object>> func:
value = await func(sectionContent).ConfigureAwait(false);
Expand All @@ -143,10 +145,10 @@ protected override async Task WriteAsync(StringRender renderer, SectionToken obj
value = func(sectionContent);
break;
case Func<dynamic, string, Func<string, Task<string>>, Task<object>> func:
value = await func(context.View, sectionContent, RenderInContextAsync(context, obj.Tags)).ConfigureAwait(false);
value = await func(lambdaView, sectionContent, RenderInContextAsync(context, obj.Tags)).ConfigureAwait(false);
break;
case Func<dynamic, string, Func<string, string>, object> func:
value = func(context.View, sectionContent, RenderInContext(context, obj.Tags));
value = func(lambdaView, sectionContent, RenderInContext(context, obj.Tags));
break;
case Func<string, Func<string, Task<string>>, Task<object>> func:
value = await func(sectionContent, RenderInContextAsync(context, obj.Tags)).ConfigureAwait(false);
Expand All @@ -165,6 +167,25 @@ protected override async Task WriteAsync(StringRender renderer, SectionToken obj
}
}

private static dynamic FindNearestObjectView(Context context)
{
var current = context;
while (current != null)
{
var view = current.View;
if (view != null && !(view is bool) && !(view is int) && !(view is long)
&& !(view is float) && !(view is double) && !(view is decimal)
&& !(view is string))
{
return view;
}

current = current.ParentContext;
}

return context.View;
}

private Func<string, string> RenderInContext(Context context, Classes.Tags tags)
{
return (str) =>
Expand Down
58 changes: 56 additions & 2 deletions test/Stubble.Core.Tests/Renderers/StringRenderer/SectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
using Stubble.Core.Settings;
using Stubble.Core.Tokens;
using Xunit;
using Stubble.Core.Contexts;
using Stubble.Core.Contexts;

namespace Stubble.Core.Tests.Renderers.StringRenderer
{
public class SectionTests : RendererTestsBase
Expand Down Expand Up @@ -328,5 +328,59 @@ public void It_Can_Render_LambdaTags_UsingOriginalTemplate()
var myStr = sr.ReadToEnd();
Assert.Equal(result, myStr);
}

[Fact]
public void It_Can_Render_ThreeArgLambda_InsideBooleanSection_WithNearestObjectView()
{
var stubble = new Stubble.Core.Builders.StubbleBuilder().Build();

dynamic capturedView = null;
var data = new Dictionary<string, object>
{
{ "showSection", true },
{ "greeting", "Hello" },
{
"myLambda",
new Func<dynamic, string, Func<string, string>, object>((ctx, tmpl, render) =>
{
capturedView = ctx;
return render(tmpl);
})
}
};

var templateStr = "{{#showSection}}{{#myLambda}}{{greeting}}{{/myLambda}}{{/showSection}}";
var result = stubble.Render(templateStr, data);

Assert.Equal("Hello", result);
// The lambda should receive the root data dictionary (nearest object view),
// not the boolean value 'true' from the showSection context.
Assert.IsAssignableFrom<IDictionary<string, object>>(capturedView);
}

[Fact]
public void It_Can_Render_ThreeArgLambda_InsideBooleanSection_DotResolvesToBool()
{
var stubble = new Stubble.Core.Builders.StubbleBuilder().Build();

var data = new Dictionary<string, object>
{
{ "showSection", true },
{
"myLambda",
new Func<dynamic, string, Func<string, string>, object>((ctx, tmpl, render) =>
{
return render(tmpl);
})
}
};

// {{.}} inside a boolean section should still resolve to "True",
// even though FindNearestObjectView passes the parent dict to the lambda.
var templateStr = "{{#showSection}}{{#myLambda}}{{.}}{{/myLambda}}{{/showSection}}";
var result = stubble.Render(templateStr, data);

Assert.Equal("True", result);
}
}
}
Loading