Skip to content
Merged
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
4 changes: 2 additions & 2 deletions source/Handlebars.Test/BasicIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ public void PathRelativeBinding(IHandlebars handlebars)
};

var result = handlebarsTemplate(data);
var actual = string.Join(" ", result.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim(' ')));
var actual = string.Join(" ", result.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()));
Assert.Equal("Garry Finch gazraa Karen Finch photobasics", actual);
}

Expand Down Expand Up @@ -501,7 +501,7 @@ public void PathRelativeBinding_WithDefaultValue(IHandlebars handlebars)
};

var result = handlebarsTemplate(data);
var actual = string.Join(" ", result.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim(' ')));
var actual = string.Join(" ", result.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()));
Assert.Equal("Garry Finch N/A Karen Finch photobasics", actual);
}

Expand Down
12 changes: 4 additions & 8 deletions source/Handlebars.Test/ComplexIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,10 @@ public void DeepIf()
var resultTrueFalse = template(trueFalse);
var resultFalseTrue = template(falseTrue);
var resultFalseFalse = template(falseFalse);
Assert.Equal(@"a is true
", resultTrueTrue);
Assert.Equal(@"a is false
", resultTrueFalse);
Assert.Equal(@"b is true
", resultFalseTrue);
Assert.Equal(@"b is false
", resultFalseFalse);
Assert.Equal("a is true\n", resultTrueTrue);
Assert.Equal("a is false\n", resultTrueFalse);
Assert.Equal("b is true\n", resultFalseTrue);
Assert.Equal("b is false\n", resultFalseFalse);
}

[Fact]
Expand Down
20 changes: 6 additions & 14 deletions source/Handlebars.Test/IssueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,11 @@ End outer partial block<br />
var callback = handlebars.Compile(view);
string result = callback(new object());

const string expected = @"Begin outer partial<br />
Begin outer partial block
<br />
Begin inner partial<br />
Begin inner partial block<br />
View<br />
End inner partial block<br />
End inner partial<br />
End outer partial block<br />
End outer partial";

// Issue #614: partial indentation is now preserved (Handlebars.js behaviour).
// Each standalone {{>@partial-block}} applies its own leading whitespace as indent
// to every line of the rendered block content.
const string expected = "Begin outer partial<br />\n Begin outer partial block\n <br />\n Begin inner partial<br />\n Begin inner partial block<br />\n View<br />\n End inner partial block<br />\n End inner partial<br />\n End outer partial block<br />\n End outer partial";

Assert.Equal(expected, result);
}

Expand Down Expand Up @@ -231,9 +225,7 @@ public void RenderingWithUnusedPartial()

var transformed = navTemplate(context).Trim();

Assert.Equal(@"<div>
<div>Menu Item: Getting Started</div>
</div>", transformed);
Assert.Equal("<div>\n <div>Menu Item: Getting Started</div>\n</div>", transformed);
}

// issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/394
Expand Down
190 changes: 190 additions & 0 deletions source/Handlebars.Test/Issues/Issue614Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System.IO;
using Xunit;

namespace HandlebarsDotNet.Test
{
/// <summary>
/// Tests for issue #614 — Partial indentation not preserved.
/// When {{> partial}} is indented with spaces/tabs on its own line, every line of the
/// rendered partial should be prefixed with that same indentation, matching Handlebars.js behavior.
/// </summary>
public class Issue614Tests
{
private readonly IHandlebars _handlebars;

public Issue614Tests()
{
_handlebars = Handlebars.Create();
}

/// <summary>
/// Spec section 20.12: Template " {{> p}}" + Partial "line1\nline2" => " line1\n line2"
/// The two leading spaces become the indentation for every line of the partial output.
/// </summary>
[Fact]
public void InlinePartialSpecExample()
{
var source = " {{> p}}";
var partialSource = "line1\nline2";

using (var reader = new StringReader(partialSource))
{
_handlebars.RegisterTemplate("p", _handlebars.Compile(reader));
}

var result = _handlebars.Compile(source)(new { });

Assert.Equal(" line1\n line2", result);
}

/// <summary>
/// The two-space indent before {{> user}} is applied to each line of the partial output.
/// The trailing newline after the standalone partial invocation is stripped (standalone behaviour).
/// </summary>
[Fact]
public void PartialIndentationWithMultiLinePartial()
{
var source = "Start\n {{> content}}\nEnd";
var partialSource = "line1\nline2\nline3";

using (var reader = new StringReader(partialSource))
{
_handlebars.RegisterTemplate("content", _handlebars.Compile(reader));
}

var result = _handlebars.Compile(source)(new { });

// TrimAfter strips the \n between the partial tag and "End", so the output is:
// "Start\n" + " line1\n line2\n line3" + "End"
Assert.Equal("Start\n line1\n line2\n line3End", result);
}

/// <summary>
/// A tab character before the partial invocation is used as the indentation.
/// </summary>
[Fact]
public void PartialIndentationWithTabCharacter()
{
var source = "Start\n\t{{> content}}\nEnd";
var partialSource = "line1\nline2";

using (var reader = new StringReader(partialSource))
{
_handlebars.RegisterTemplate("content", _handlebars.Compile(reader));
}

var result = _handlebars.Compile(source)(new { });

Assert.Equal("Start\n\tline1\n\tline2End", result);
}

/// <summary>
/// A partial with no preceding whitespace receives no indentation.
/// The newline that follows the standalone partial tag is stripped.
/// </summary>
[Fact]
public void PartialWithNoIndentationUnchanged()
{
var source = "Hello\n{{> greeting}}\nBye";
var partialSource = "World";

using (var reader = new StringReader(partialSource))
{
_handlebars.RegisterTemplate("greeting", _handlebars.Compile(reader));
}

var result = _handlebars.Compile(source)(new { });

// Standalone with no indent: TrimAfter strips \nBye → Bye, no indent added.
Assert.Equal("Hello\nWorldBye", result);
}

/// <summary>
/// The indentation is applied inside a block helper iteration.
/// A single-line partial produces indented output per iteration;
/// iterations are not separated because the newline after {{> user}} is stripped.
/// </summary>
[Fact]
public void PartialIndentationIsAppliedInsideBlock()
{
var source = "<h2>Names</h2>\n{{#names}}\n {{> user}}\n{{/names}}";
var partialSource = "<strong>{{name}}</strong>";

using (var reader = new StringReader(partialSource))
{
_handlebars.RegisterTemplate("user", _handlebars.Compile(reader));
}

var template = _handlebars.Compile(source);
var data = new
{
names = new[]
{
new { name = "Karen" },
new { name = "Jon" }
}
};

var result = template(data);

// The standalone \n after {{> user}} is stripped; iterations are concatenated directly.
// Each partial invocation outputs " <strong>Name</strong>" (indent applied).
Assert.Equal("<h2>Names</h2>\n <strong>Karen</strong> <strong>Jon</strong>", result);
}

/// <summary>
/// A partial whose source uses Windows-style \r\n line endings (e.g. checked out on Windows
/// with git autocrlf=true, or produced by a StringWriter whose NewLine is \r\n) is normalised
/// to \n in the indented output. The library always emits \n as the line separator so that
/// rendered output is identical across platforms.
/// </summary>
[Fact]
public void PartialWithCrLfLineEndingsNormalisedToLf()
{
var source = " {{> p}}";
var partialSource = "line1\r\nline2\r\nline3"; // Windows-style \r\n

using (var reader = new StringReader(partialSource))
{
_handlebars.RegisterTemplate("p", _handlebars.Compile(reader));
}

var result = _handlebars.Compile(source)(new { });

// \r\n in the partial source is normalised to \n; every line gets the indent.
Assert.Equal(" line1\n line2\n line3", result);
}

/// <summary>
/// A multi-line partial called inside an iteration — each line of each iteration is indented.
/// </summary>
[Fact]
public void MultiLinePartialIndentationInsideBlock()
{
var source = "{{#items}}\n {{> row}}\n{{/items}}";
var partialSource = "- {{name}}\n ({{desc}})";

using (var reader = new StringReader(partialSource))
{
_handlebars.RegisterTemplate("row", _handlebars.Compile(reader));
}

var template = _handlebars.Compile(source);
var data = new
{
items = new[]
{
new { name = "A", desc = "alpha" },
new { name = "B", desc = "beta" }
}
};

var result = template(data);

// Each 2-line partial gets " " prepended to both lines.
// The newline between the partial tag and the next iteration/closing tag is stripped.
Assert.Equal(" - A\n (alpha) - B\n (beta)", result);
}

}
}
8 changes: 4 additions & 4 deletions source/Handlebars.Test/ReadmeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ public void RegisterBlockHelper()
{"Chewy", "hamster" }
};

var template = "{{#each this}}The animal, {{@key}}, {{#StringEqualityBlockHelper @value 'dog'}}is a dog{{else}}is not a dog{{/StringEqualityBlockHelper}}.\r\n{{/each}}";
var template = "{{#each this}}The animal, {{@key}}, {{#StringEqualityBlockHelper @value 'dog'}}is a dog{{else}}is not a dog{{/StringEqualityBlockHelper}}.\n{{/each}}";
var compiledTemplate = handlebars.Compile(template);
string templateOutput = compiledTemplate(animals);

Assert.Equal(
"The animal, Fluffy, is not a dog.\r\n" +
"The animal, Fido, is a dog.\r\n" +
"The animal, Chewy, is not a dog.\r\n",
"The animal, Fluffy, is not a dog.\n" +
"The animal, Fido, is a dog.\n" +
"The animal, Chewy, is not a dog.\n",
templateOutput
);
}
Expand Down
Loading
Loading