templates support
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Configuration;
|
||||
|
||||
public class PrinterProfileTests
|
||||
{
|
||||
[Fact]
|
||||
public void Default_Profile_HasExpectedValues()
|
||||
{
|
||||
var profile = PrinterProfile.Default;
|
||||
|
||||
Assert.Equal("default", profile.Id);
|
||||
Assert.Equal(48, profile.LineWidth);
|
||||
Assert.Equal(24, profile.BigLineWidth);
|
||||
Assert.False(profile.SupportsRed);
|
||||
}
|
||||
}
|
||||
|
||||
public class PrinterProfileRegistryTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetProfile_TmT30III_ReturnsCorrectProfile()
|
||||
{
|
||||
var registry = new PrinterProfileRegistry();
|
||||
var profile = registry.GetProfile(0x01);
|
||||
|
||||
Assert.Equal("tm-t30iii", profile.Id);
|
||||
Assert.Equal(48, profile.LineWidth);
|
||||
Assert.Equal(24, profile.BigLineWidth);
|
||||
Assert.False(profile.SupportsRed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetProfile_TmU220II_0x0D_ReturnsCorrectProfile()
|
||||
{
|
||||
var registry = new PrinterProfileRegistry();
|
||||
var profile = registry.GetProfile(0x0D);
|
||||
|
||||
Assert.Equal("tm-u220ii", profile.Id);
|
||||
Assert.Equal(33, profile.LineWidth);
|
||||
Assert.Equal(20, profile.BigLineWidth);
|
||||
Assert.True(profile.SupportsRed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetProfile_TmU220II_0x13_ReturnsCorrectProfile()
|
||||
{
|
||||
var registry = new PrinterProfileRegistry();
|
||||
var profile = registry.GetProfile(0x13);
|
||||
|
||||
Assert.Equal("tm-u220ii", profile.Id);
|
||||
Assert.Equal(33, profile.LineWidth);
|
||||
Assert.Equal(20, profile.BigLineWidth);
|
||||
Assert.True(profile.SupportsRed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetProfile_UnknownPrinter_ReturnsDefault()
|
||||
{
|
||||
var registry = new PrinterProfileRegistry();
|
||||
var profile = registry.GetProfile(0xFF);
|
||||
|
||||
Assert.Equal(PrinterProfile.Default.LineWidth, profile.LineWidth);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetProfile_ByName_ReturnsCorrectProfile()
|
||||
{
|
||||
var registry = new PrinterProfileRegistry();
|
||||
var profile = registry.GetProfile("tm-t30iii");
|
||||
|
||||
Assert.Equal("tm-t30iii", profile.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Register_CustomProfile_CanBeRetrieved()
|
||||
{
|
||||
var registry = new PrinterProfileRegistry();
|
||||
var customProfile = new PrinterProfile("custom", "Custom Printer", 40, 20, true);
|
||||
registry.Register(0xAA, customProfile);
|
||||
|
||||
var retrieved = registry.GetProfile(0xAA);
|
||||
Assert.Equal("custom", retrieved.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAllProfiles_ReturnsDistinctProfiles()
|
||||
{
|
||||
var registry = new PrinterProfileRegistry();
|
||||
var profiles = registry.GetAllProfiles().ToList();
|
||||
|
||||
// Should have TM-T30III and TM-U220II (distinct)
|
||||
Assert.Equal(2, profiles.Count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Configuration;
|
||||
|
||||
public class TemplateResolverTests
|
||||
{
|
||||
[Fact]
|
||||
public void Resolve_ExactMatch_ReturnsExactTemplate()
|
||||
{
|
||||
var assignments = new[]
|
||||
{
|
||||
new TemplateAssignment(1, "tm-t30iii", "kitchen-t30.template"),
|
||||
new TemplateAssignment(1, null, "kitchen-default.template")
|
||||
};
|
||||
var resolver = new TemplateResolver(assignments);
|
||||
|
||||
var result = resolver.Resolve(1, "tm-t30iii");
|
||||
|
||||
Assert.Equal("kitchen-t30.template", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_TypeOnlyMatch_ReturnsTypeTemplate()
|
||||
{
|
||||
var assignments = new[]
|
||||
{
|
||||
new TemplateAssignment(1, null, "kitchen-default.template"),
|
||||
new TemplateAssignment(2, null, "bar-default.template")
|
||||
};
|
||||
var resolver = new TemplateResolver(assignments);
|
||||
|
||||
var result = resolver.Resolve(1, "unknown-profile");
|
||||
|
||||
Assert.Equal("kitchen-default.template", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_NoMatch_ReturnsFallback()
|
||||
{
|
||||
var assignments = new[]
|
||||
{
|
||||
new TemplateAssignment(1, null, "kitchen-default.template")
|
||||
};
|
||||
var resolver = new TemplateResolver(assignments, "fallback.template");
|
||||
|
||||
var result = resolver.Resolve(99, "unknown-profile");
|
||||
|
||||
Assert.Equal("fallback.template", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_PreferExactOverTypeOnly()
|
||||
{
|
||||
var assignments = new[]
|
||||
{
|
||||
new TemplateAssignment(1, null, "type-only.template"),
|
||||
new TemplateAssignment(1, "tm-t30iii", "exact.template")
|
||||
};
|
||||
var resolver = new TemplateResolver(assignments);
|
||||
|
||||
var result = resolver.Resolve(1, "tm-t30iii");
|
||||
|
||||
Assert.Equal("exact.template", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_WithRegistry_MapsProfileCorrectly()
|
||||
{
|
||||
var registry = new PrinterProfileRegistry();
|
||||
var assignments = new[]
|
||||
{
|
||||
new TemplateAssignment(1, "tm-t30iii", "kitchen-t30.template"),
|
||||
new TemplateAssignment(1, "tm-u220ii", "kitchen-u220.template")
|
||||
};
|
||||
var resolver = new TemplateResolver(assignments);
|
||||
|
||||
var result = resolver.Resolve(1, 0x01, registry);
|
||||
|
||||
Assert.Equal("kitchen-t30.template", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadConfiguration_ParsesCorrectly()
|
||||
{
|
||||
var json = """
|
||||
{
|
||||
"assignments": [
|
||||
{"receiptType": 1, "profileId": "tm-t30iii", "template": "kitchen.template"},
|
||||
{"receiptType": 2, "profileId": null, "template": "bar.template"}
|
||||
],
|
||||
"fallbackTemplate": "default.template"
|
||||
}
|
||||
""";
|
||||
|
||||
var config = TemplateConfiguration.LoadFromJson(json);
|
||||
|
||||
Assert.Equal(2, config.Assignments.Count);
|
||||
Assert.Equal("default.template", config.FallbackTemplate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using Inspectron.Epson.PrintServer.Printers.Utils;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Helpers;
|
||||
|
||||
public static class PrintCommandAssertions
|
||||
{
|
||||
public static void AssertCommandExists(
|
||||
List<PrintCommand> commands,
|
||||
string textContains,
|
||||
bool? isBold = null,
|
||||
bool? isBig = null,
|
||||
bool? isTall = null,
|
||||
bool? isRed = null)
|
||||
{
|
||||
var matching = commands.Where(c => c.Text.Contains(textContains)).ToList();
|
||||
|
||||
if (matching.Count == 0)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException($"No command found containing text: '{textContains}'");
|
||||
}
|
||||
|
||||
foreach (var cmd in matching)
|
||||
{
|
||||
if (isBold.HasValue && cmd.IsBold != isBold.Value)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Command with text '{textContains}' has IsBold={cmd.IsBold}, expected {isBold.Value}");
|
||||
}
|
||||
if (isBig.HasValue && cmd.IsBig != isBig.Value)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Command with text '{textContains}' has IsBig={cmd.IsBig}, expected {isBig.Value}");
|
||||
}
|
||||
if (isTall.HasValue && cmd.IsTall != isTall.Value)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Command with text '{textContains}' has IsTall={cmd.IsTall}, expected {isTall.Value}");
|
||||
}
|
||||
if (isRed.HasValue && cmd.IsRed != isRed.Value)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Command with text '{textContains}' has IsRed={cmd.IsRed}, expected {isRed.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void AssertSequence(List<PrintCommand> commands, params string[] expectedTexts)
|
||||
{
|
||||
var commandTexts = commands.Select(c => c.Text).ToList();
|
||||
int searchIndex = 0;
|
||||
|
||||
foreach (var expected in expectedTexts)
|
||||
{
|
||||
var foundIndex = -1;
|
||||
for (int i = searchIndex; i < commandTexts.Count; i++)
|
||||
{
|
||||
if (commandTexts[i].Contains(expected))
|
||||
{
|
||||
foundIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundIndex < 0)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Expected text '{expected}' not found in sequence after index {searchIndex}");
|
||||
}
|
||||
|
||||
searchIndex = foundIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AssertCommandCount(List<PrintCommand> commands, string textContains, int expectedCount)
|
||||
{
|
||||
var count = commands.Count(c => c.Text.Contains(textContains));
|
||||
if (count != expectedCount)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Expected {expectedCount} commands containing '{textContains}', found {count}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Inspectron.Epson.Templates\Inspectron.Epson.Templates.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,193 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
using Inspectron.Epson.Templates.Interpreter;
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
using Inspectron.Epson.Templates.Language.Nodes;
|
||||
using Inspectron.Epson.Templates.Tests.Helpers;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Integration;
|
||||
|
||||
public class KitchenReceiptTests
|
||||
{
|
||||
private readonly PrinterProfile _profile = new("tm-t30iii", "TM-T30III", 48, 24, false);
|
||||
|
||||
private const string KitchenReceiptJson = """
|
||||
{
|
||||
"Title": "Restaurant Test",
|
||||
"TransactionDateTime": "2024-03-15T14:30:00",
|
||||
"ReceiptNumber": "12345",
|
||||
"WaiterName": "John Doe",
|
||||
"WaiterId": "W001",
|
||||
"TableNumber": "5",
|
||||
"SpecialInstruction": "Rush Order",
|
||||
"Gangs": [
|
||||
{
|
||||
"Id": 1,
|
||||
"Name": "Starters",
|
||||
"Dishes": [
|
||||
{
|
||||
"Number": 2,
|
||||
"Name": "Caesar Salad",
|
||||
"Modifications": {
|
||||
"Removed": ["Croutons"],
|
||||
"Added": ["Extra Dressing"]
|
||||
},
|
||||
"Comment": "No anchovies"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Dishes": [
|
||||
{
|
||||
"Number": 1,
|
||||
"Name": "Grilled Salmon",
|
||||
"Modifications": {
|
||||
"Removed": [],
|
||||
"Added": []
|
||||
},
|
||||
"Comment": null
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
private const string KitchenTemplate = """
|
||||
#red,big,tall,center# {Title} #
|
||||
---
|
||||
|
||||
#center# {TransactionDateTime:dd-MMM-yy HH:mm} Nr.:{ReceiptNumber} #
|
||||
#center# {WaiterName} #
|
||||
#center# {WaiterId} #
|
||||
#big,bold,center# Tisch: {TableNumber} #
|
||||
|
||||
@if SpecialInstruction
|
||||
|
||||
#big,bold,center# {SpecialInstruction} #
|
||||
|
||||
@end
|
||||
---
|
||||
@foreach gang in Gangs
|
||||
#red,big,tall,center# {gang.Id}. {gang.Name} #
|
||||
@foreach dish in gang.Dishes
|
||||
#tall# {dish.Number}x {dish.Name} #
|
||||
@if dish.Modifications.Removed.count > 0
|
||||
@foreach removed in dish.Modifications.Removed
|
||||
#bold,tall# - {removed} #
|
||||
@end
|
||||
@end
|
||||
@if dish.Modifications.Added.count > 0
|
||||
@foreach added in dish.Modifications.Added
|
||||
#bold,tall# + {added} #
|
||||
@end
|
||||
@end
|
||||
@if dish.Comment
|
||||
#bold,tall# Comment: {dish.Comment} #
|
||||
@end
|
||||
@end
|
||||
@end
|
||||
@if Gangs.count > 0
|
||||
---
|
||||
@end
|
||||
@foreach dish in Dishes
|
||||
#tall# {dish.Number}x {dish.Name} #
|
||||
@end
|
||||
""";
|
||||
|
||||
private TemplateNode Parse(string source)
|
||||
{
|
||||
var lexer = new Lexer(source);
|
||||
var parser = new Parser(lexer.Tokenize().Tokens);
|
||||
return parser.Parse().Template;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KitchenReceipt_RendersFully()
|
||||
{
|
||||
var template = Parse(KitchenTemplate);
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, KitchenReceiptJson);
|
||||
|
||||
// Verify key elements are present
|
||||
Assert.Contains(commands, c => c.Text.Contains("Restaurant Test"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("John Doe"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("Tisch: 5"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("Rush Order"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("1. Starters"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("2x Caesar Salad"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("- Croutons"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("+ Extra Dressing"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("Comment: No anchovies"));
|
||||
Assert.Contains(commands, c => c.Text.Contains("1x Grilled Salmon"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KitchenReceipt_AppliesStyles()
|
||||
{
|
||||
var template = Parse(KitchenTemplate);
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, KitchenReceiptJson);
|
||||
|
||||
// Title should be big and tall
|
||||
var titleCmd = commands.FirstOrDefault(c => c.Text.Contains("Restaurant Test"));
|
||||
Assert.NotNull(titleCmd);
|
||||
Assert.True(titleCmd.IsBig);
|
||||
Assert.True(titleCmd.IsTall);
|
||||
|
||||
// Table should be bold and big
|
||||
var tableCmd = commands.FirstOrDefault(c => c.Text.Contains("Tisch: 5"));
|
||||
Assert.NotNull(tableCmd);
|
||||
Assert.True(tableCmd.IsBold);
|
||||
Assert.True(tableCmd.IsBig);
|
||||
|
||||
// Modifications should be bold and tall
|
||||
var modCmd = commands.FirstOrDefault(c => c.Text.Contains("- Croutons"));
|
||||
Assert.NotNull(modCmd);
|
||||
Assert.True(modCmd.IsBold);
|
||||
Assert.True(modCmd.IsTall);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KitchenReceipt_GeneratesSeparators()
|
||||
{
|
||||
var template = Parse(KitchenTemplate);
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, KitchenReceiptJson);
|
||||
|
||||
var separatorCount = commands.Count(c => c.Text.All(ch => ch == '-') && c.Text.Length == 48);
|
||||
Assert.True(separatorCount >= 2, $"Expected at least 2 separators, found {separatorCount}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KitchenReceipt_FormatsDateTime()
|
||||
{
|
||||
var template = Parse(KitchenTemplate);
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, KitchenReceiptJson);
|
||||
|
||||
Assert.Contains(commands, c => c.Text.Contains("15-Mar-24") || c.Text.Contains("15-Mär-24"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KitchenReceipt_WithoutSpecialInstruction_OmitsSection()
|
||||
{
|
||||
var jsonWithoutInstruction = """
|
||||
{
|
||||
"Title": "Test",
|
||||
"TransactionDateTime": "2024-03-15T14:30:00",
|
||||
"ReceiptNumber": "123",
|
||||
"WaiterName": "John",
|
||||
"WaiterId": "W001",
|
||||
"TableNumber": "1",
|
||||
"SpecialInstruction": null,
|
||||
"Gangs": [],
|
||||
"Dishes": []
|
||||
}
|
||||
""";
|
||||
|
||||
var template = Parse(KitchenTemplate);
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, jsonWithoutInstruction);
|
||||
|
||||
// Should not have any content that looks like special instruction
|
||||
Assert.DoesNotContain(commands, c => c.Text.Contains("Rush"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
using Inspectron.Epson.Templates.Interpreter;
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
using Inspectron.Epson.Templates.Language.Nodes;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Interpreter;
|
||||
|
||||
public class InterpreterBindingTests
|
||||
{
|
||||
private readonly PrinterProfile _profile = new("test", "Test Printer", 48, 24, false);
|
||||
|
||||
private TemplateNode Parse(string source)
|
||||
{
|
||||
var lexer = new Lexer(source);
|
||||
var parser = new Parser(lexer.Tokenize().Tokens);
|
||||
return parser.Parse().Template;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_SimpleBinding_ResolvesValue()
|
||||
{
|
||||
var template = Parse("{Name}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Name": "John"}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "John");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_NestedBinding_ResolvesNestedValue()
|
||||
{
|
||||
var template = Parse("{Person.Name}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Person": {"Name": "Jane"}}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Jane");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_NumericBinding_OutputsNumber()
|
||||
{
|
||||
var template = Parse("{Amount}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Amount": 42}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "42");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_NumericBinding_WithFormat_FormatsNumber()
|
||||
{
|
||||
var template = Parse("{Price:F2}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Price": 19.5}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "19.50");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_DateBinding_WithFormat_FormatsDate()
|
||||
{
|
||||
var template = Parse("{Date:dd-MMM-yy}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Date": "2024-03-15T14:30:00"}""");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("Mar"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.Contains("15", cmd.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_MissingBinding_OutputsEmptyString()
|
||||
{
|
||||
var template = Parse("{Missing}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Other": "value"}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_NullBinding_OutputsEmptyString()
|
||||
{
|
||||
var template = Parse("{Value}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Value": null}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_ArrayCountBinding_OutputsCount()
|
||||
{
|
||||
var template = Parse("{Items.count}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": [1, 2, 3, 4, 5]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "5");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_BoolBinding_OutputsTrue()
|
||||
{
|
||||
var template = Parse("{Flag}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Flag": true}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "true");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_BoolBinding_OutputsFalse()
|
||||
{
|
||||
var template = Parse("{Flag}");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Flag": false}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "false");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_BindingInStyledText_AppliesStyleToResolvedValue()
|
||||
{
|
||||
var template = Parse("#bold# {Name} #");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Name": "Test"}""");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("Test"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.True(cmd.IsBold);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
using Inspectron.Epson.Templates.Interpreter;
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
using Inspectron.Epson.Templates.Language.Nodes;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Interpreter;
|
||||
|
||||
public class InterpreterConditionTests
|
||||
{
|
||||
private readonly PrinterProfile _profile = new("test", "Test Printer", 48, 24, false);
|
||||
|
||||
private TemplateNode Parse(string source)
|
||||
{
|
||||
var lexer = new Lexer(source);
|
||||
var parser = new Parser(lexer.Tokenize().Tokens);
|
||||
return parser.Parse().Template;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfTruthy_ExecutesBody()
|
||||
{
|
||||
var template = Parse("@if HasValue\nYes\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"HasValue": true}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Yes");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfFalsy_SkipsBody()
|
||||
{
|
||||
var template = Parse("@if HasValue\nYes\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"HasValue": false}""");
|
||||
|
||||
Assert.DoesNotContain(commands, c => c.Text == "Yes");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfStringEmpty_IsFalsy()
|
||||
{
|
||||
var template = Parse("@if Name\nHas Name\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Name": ""}""");
|
||||
|
||||
Assert.DoesNotContain(commands, c => c.Text == "Has Name");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfStringNotEmpty_IsTruthy()
|
||||
{
|
||||
var template = Parse("@if Name\nHas Name\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Name": "John"}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Has Name");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfArrayEmpty_IsFalsy()
|
||||
{
|
||||
var template = Parse("@if Items\nHas Items\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": []}""");
|
||||
|
||||
Assert.DoesNotContain(commands, c => c.Text == "Has Items");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfArrayNotEmpty_IsTruthy()
|
||||
{
|
||||
var template = Parse("@if Items\nHas Items\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": [1, 2]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Has Items");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfNegation_InvertsCondition()
|
||||
{
|
||||
var template = Parse("@if !HasValue\nNo Value\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"HasValue": false}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "No Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfEqualsComparison_Works()
|
||||
{
|
||||
var template = Parse("@if Status == \"active\"\nActive\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Status": "active"}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Active");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfNotEqualsComparison_Works()
|
||||
{
|
||||
var template = Parse("@if Status != \"inactive\"\nNot Inactive\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Status": "active"}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Not Inactive");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfGreaterThanComparison_Works()
|
||||
{
|
||||
var template = Parse("@if Count > 5\nMany Items\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Count": 10}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Many Items");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfLessThanComparison_Works()
|
||||
{
|
||||
var template = Parse("@if Count < 5\nFew Items\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Count": 2}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Few Items");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfElse_ExecutesElseWhenFalse()
|
||||
{
|
||||
var template = Parse("@if HasValue\nYes\n@else\nNo\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"HasValue": false}""");
|
||||
|
||||
Assert.DoesNotContain(commands, c => c.Text == "Yes");
|
||||
Assert.Contains(commands, c => c.Text == "No");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_IfElseIf_ExecutesCorrectBranch()
|
||||
{
|
||||
var template = Parse("@if Value == 1\nOne\n@elseif Value == 2\nTwo\n@else\nOther\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Value": 2}""");
|
||||
|
||||
Assert.DoesNotContain(commands, c => c.Text == "One");
|
||||
Assert.Contains(commands, c => c.Text == "Two");
|
||||
Assert.DoesNotContain(commands, c => c.Text == "Other");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_ArrayCountComparison_Works()
|
||||
{
|
||||
var template = Parse("@if Items.count > 0\nHas Items\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": [1, 2, 3]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Has Items");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
using Inspectron.Epson.Templates.Interpreter;
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
using Inspectron.Epson.Templates.Language.Nodes;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Interpreter;
|
||||
|
||||
public class InterpreterLoopTests
|
||||
{
|
||||
private readonly PrinterProfile _profile = new("test", "Test Printer", 48, 24, false);
|
||||
|
||||
private TemplateNode Parse(string source)
|
||||
{
|
||||
var lexer = new Lexer(source);
|
||||
var parser = new Parser(lexer.Tokenize().Tokens);
|
||||
return parser.Parse().Template;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_IteratesOverArray()
|
||||
{
|
||||
var template = Parse("@foreach item in Items\n{item}\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B", "C"]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "A");
|
||||
Assert.Contains(commands, c => c.Text == "B");
|
||||
Assert.Contains(commands, c => c.Text == "C");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_AccessesObjectProperties()
|
||||
{
|
||||
var template = Parse("@foreach item in Items\n{item.Name}\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": [{"Name": "First"}, {"Name": "Second"}]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "First");
|
||||
Assert.Contains(commands, c => c.Text == "Second");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_EmptyArray_NoOutput()
|
||||
{
|
||||
var template = Parse("@foreach item in Items\n{item}\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": []}""");
|
||||
|
||||
Assert.DoesNotContain(commands, c => !string.IsNullOrWhiteSpace(c.Text));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_IndexMetadata_Works()
|
||||
{
|
||||
var template = Parse("@foreach item in Items\n{_index}:{item}\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B"]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "0");
|
||||
Assert.Contains(commands, c => c.Text == "1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_NumberMetadata_Works()
|
||||
{
|
||||
var template = Parse("@foreach item in Items\n{_number}. {item}\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B"]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "1");
|
||||
Assert.Contains(commands, c => c.Text == "2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_FirstMetadata_Works()
|
||||
{
|
||||
var template = Parse("@foreach item in Items\n@if _first\nFirst: {item}\n@end\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B", "C"]}""");
|
||||
|
||||
var firstCmds = commands.Where(c => c.Text.Contains("First")).ToList();
|
||||
Assert.Single(firstCmds);
|
||||
Assert.Contains(commands, c => c.Text == "A");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_LastMetadata_Works()
|
||||
{
|
||||
var template = Parse("@foreach item in Items\n@if _last\nLast: {item}\n@end\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Items": ["A", "B", "C"]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text.Contains("Last"));
|
||||
Assert.Contains(commands, c => c.Text == "C");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_NestedForeach_Works()
|
||||
{
|
||||
var template = Parse("@foreach group in Groups\n@foreach item in group.Items\n{item}\n@end\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template,
|
||||
"""{"Groups": [{"Items": ["A", "B"]}, {"Items": ["C", "D"]}]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "A");
|
||||
Assert.Contains(commands, c => c.Text == "B");
|
||||
Assert.Contains(commands, c => c.Text == "C");
|
||||
Assert.Contains(commands, c => c.Text == "D");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_AccessesParentContext()
|
||||
{
|
||||
var template = Parse("@foreach item in Items\n{Title}: {item}\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Title": "List", "Items": ["A", "B"]}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "List");
|
||||
Assert.Contains(commands, c => c.Text == "A");
|
||||
Assert.Contains(commands, c => c.Text == "B");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Foreach_NestedPath_Works()
|
||||
{
|
||||
var template = Parse("@foreach item in Data.Items\n{item.Name}\n@end");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template,
|
||||
"""{"Data": {"Items": [{"Name": "First"}, {"Name": "Second"}]}}""");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "First");
|
||||
Assert.Contains(commands, c => c.Text == "Second");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
using Inspectron.Epson.Templates.Interpreter;
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
using Inspectron.Epson.Templates.Language.Nodes;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Interpreter;
|
||||
|
||||
public class InterpreterRowTests
|
||||
{
|
||||
private readonly PrinterProfile _profile = new("test", "Test Printer", 48, 24, false);
|
||||
|
||||
private TemplateNode Parse(string source)
|
||||
{
|
||||
var lexer = new Lexer(source);
|
||||
var parser = new Parser(lexer.Tokenize().Tokens);
|
||||
return parser.Parse().Template;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Row_CombinesColumns()
|
||||
{
|
||||
var template = Parse("@row\n|20|Name|10|Value\n@endrow");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
// Should have a single command with combined column text
|
||||
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Name") && c.Text.Contains("Value"));
|
||||
Assert.NotNull(rowCmd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Row_LeftAlignsPaddsRight()
|
||||
{
|
||||
var template = Parse("@row\n|10|Hi\n@endrow");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Hi"));
|
||||
Assert.NotNull(rowCmd);
|
||||
Assert.Equal(10, rowCmd.Text.Length);
|
||||
Assert.StartsWith("Hi", rowCmd.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Row_RightAlignsPaddsLeft()
|
||||
{
|
||||
var template = Parse("@row\n|10,right|Hi\n@endrow");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Hi"));
|
||||
Assert.NotNull(rowCmd);
|
||||
Assert.Equal(10, rowCmd.Text.Length);
|
||||
Assert.EndsWith("Hi", rowCmd.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Row_CenterAlignsCentersText()
|
||||
{
|
||||
var template = Parse("@row\n|10,center|Hi\n@endrow");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Hi"));
|
||||
Assert.NotNull(rowCmd);
|
||||
Assert.Equal(10, rowCmd.Text.Length);
|
||||
Assert.StartsWith(" Hi", rowCmd.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Row_TruncatesLongText()
|
||||
{
|
||||
var template = Parse("@row\n|5|HelloWorld\n@endrow");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Hello"));
|
||||
Assert.NotNull(rowCmd);
|
||||
Assert.Equal(5, rowCmd.Text.Length);
|
||||
Assert.Equal("Hello", rowCmd.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Row_WithBindings_ResolvesValues()
|
||||
{
|
||||
var template = Parse("@row\n|20|{Name}|10,right|{Price:F2}\n@endrow");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, """{"Name": "Item", "Price": 9.99}""");
|
||||
|
||||
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("Item") && c.Text.Contains("9.99"));
|
||||
Assert.NotNull(rowCmd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_Row_MultipleColumns_CorrectTotalWidth()
|
||||
{
|
||||
var template = Parse("@row\n|10|A|10|B|10|C\n@endrow");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var rowCmd = commands.FirstOrDefault(c => c.Text.Contains("A") && c.Text.Contains("B") && c.Text.Contains("C"));
|
||||
Assert.NotNull(rowCmd);
|
||||
Assert.Equal(30, rowCmd.Text.Length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
using Inspectron.Epson.Templates.Interpreter;
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
using Inspectron.Epson.Templates.Language.Nodes;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Interpreter;
|
||||
|
||||
public class InterpreterTextTests
|
||||
{
|
||||
private readonly PrinterProfile _profile = new("test", "Test Printer", 48, 24, false);
|
||||
|
||||
private TemplateNode Parse(string source)
|
||||
{
|
||||
var lexer = new Lexer(source);
|
||||
var parser = new Parser(lexer.Tokenize().Tokens);
|
||||
return parser.Parse().Template;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_PlainText_OutputsText()
|
||||
{
|
||||
var template = Parse("Hello World");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
Assert.Contains(commands, c => c.Text == "Hello World");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_BoldStyle_SetsBoldFlag()
|
||||
{
|
||||
var template = Parse("#bold# text #");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("text"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.True(cmd.IsBold);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_BigStyle_SetsBigFlag()
|
||||
{
|
||||
var template = Parse("#big# text #");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("text"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.True(cmd.IsBig);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_TallStyle_SetsTallFlag()
|
||||
{
|
||||
var template = Parse("#tall# text #");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("text"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.True(cmd.IsTall);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_RedStyle_SetsRedFlag_WhenSupported()
|
||||
{
|
||||
var profileWithRed = new PrinterProfile("test", "Test", 48, 24, true);
|
||||
var template = Parse("#red# text #");
|
||||
var interpreter = new TemplateInterpreter(profileWithRed);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("text"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.True(cmd.IsRed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_RedStyle_IgnoresRedFlag_WhenNotSupported()
|
||||
{
|
||||
var profileNoRed = new PrinterProfile("test", "Test", 48, 24, false);
|
||||
var template = Parse("#red# text #");
|
||||
var interpreter = new TemplateInterpreter(profileNoRed);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("text"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.False(cmd.IsRed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_CenterStyle_CentersText()
|
||||
{
|
||||
var template = Parse("#center# Hi #");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("Hi"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.StartsWith(" ", cmd.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_MultipleStyles_CombinesFlags()
|
||||
{
|
||||
var template = Parse("#bold,big,tall# text #");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("text"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.True(cmd.IsBold);
|
||||
Assert.True(cmd.IsBig);
|
||||
Assert.True(cmd.IsTall);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_DashSeparator_OutputsFullWidthLine()
|
||||
{
|
||||
var template = Parse("---");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains('-'));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.Equal(48, cmd.Text.Length);
|
||||
Assert.True(cmd.Text.All(c => c == '-'));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_EqualsSeparator_OutputsFullWidthLine()
|
||||
{
|
||||
var template = Parse("===");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains('='));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.Equal(48, cmd.Text.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interpret_SpacingStyle_SetsLineSpacing()
|
||||
{
|
||||
var template = Parse("#spacing:50# text #");
|
||||
var interpreter = new TemplateInterpreter(_profile);
|
||||
var commands = interpreter.Interpret(template, "{}");
|
||||
|
||||
var cmd = commands.FirstOrDefault(c => c.Text.Contains("text"));
|
||||
Assert.NotNull(cmd);
|
||||
Assert.Equal(50, cmd.SetLineSpacing);
|
||||
}
|
||||
}
|
||||
219
Inspectron.Epson.Templates.Tests/Language/CommentTests.cs
Normal file
219
Inspectron.Epson.Templates.Tests/Language/CommentTests.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Language;
|
||||
|
||||
public class CommentTests
|
||||
{
|
||||
#region Single-line comments
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_SingleLineComment_ProducesNoTextTokens()
|
||||
{
|
||||
var lexer = new Lexer("@* This is a comment");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_InlineSingleLineComment_ProducesNoTextTokens()
|
||||
{
|
||||
var lexer = new Lexer("@* This is a comment *@");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_IndentedSingleLineComment_IsRecognizedAsComment()
|
||||
{
|
||||
var lexer = new Lexer(" @* indented comment");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_TextBeforeAndAfterCommentLine_IsPreserved()
|
||||
{
|
||||
var lexer = new Lexer("Before\n@* comment\nAfter");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Before");
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "After");
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("comment"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_InlineCommentWithContentAfter_PreservesContentAfter()
|
||||
{
|
||||
var lexer = new Lexer("@* comment *@After");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "After");
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("comment"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Multi-line comments
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_MultiLineComment_ProducesNoTextTokens()
|
||||
{
|
||||
var lexer = new Lexer("@*\nThis is multi-line\ncomment text\n*@");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_MultiLineComment_ContentBeforeAndAfterPreserved()
|
||||
{
|
||||
var lexer = new Lexer("Before\n@*\ncomment\n*@\nAfter");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Before");
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "After");
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("comment"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_UnclosedMultiLineComment_ProducesError()
|
||||
{
|
||||
var lexer = new Lexer("@*\nThis comment is never closed");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(result.Errors, e => e.Message.Contains("Unclosed multi-line comment"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_MultiLineCommentWithContentAfterCloser_PreservesContent()
|
||||
{
|
||||
var lexer = new Lexer("@*\ncomment\n*@After");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "After");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edge cases
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_StarSeparator_StillWorksNotConfusedWithComment()
|
||||
{
|
||||
var lexer = new Lexer("***");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.StarSeparator);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_DirectivesInsideComment_AreIgnored()
|
||||
{
|
||||
var lexer = new Lexer("@*\n@if condition\n@foreach item in items\n*@");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.If);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Foreach);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_BindingsInsideComment_AreIgnored()
|
||||
{
|
||||
var lexer = new Lexer("@* {PropertyName} *@");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Binding);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_CommentWithMultipleStars_HandledCorrectly()
|
||||
{
|
||||
var lexer = new Lexer("@*** This has extra stars ***@");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("stars"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_EmptyMultiLineComment_HandledCorrectly()
|
||||
{
|
||||
var lexer = new Lexer("@*\n*@");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_CommentInComplexTemplate_WorksCorrectly()
|
||||
{
|
||||
var template = @"#bold,center# Title #
|
||||
@* This is a header comment *@
|
||||
---
|
||||
@if HasItems
|
||||
@* Loop through items *@
|
||||
@foreach item in Items
|
||||
{item.Name}
|
||||
@end
|
||||
@end";
|
||||
var lexer = new Lexer(template);
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.StyleStart);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.DashSeparator);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.If);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Foreach);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("comment"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_MultipleCommentsInTemplate_AllStripped()
|
||||
{
|
||||
var template = @"Line 1
|
||||
@* Comment 1 *@
|
||||
Line 2
|
||||
@* Comment 2
|
||||
still comment
|
||||
*@
|
||||
Line 3";
|
||||
var lexer = new Lexer(template);
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Line 1");
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Line 2");
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Line 3");
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.Text && t.Value.Contains("Comment"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_SeparatorInsideMultiLineComment_IsIgnored()
|
||||
{
|
||||
var lexer = new Lexer("@*\n---\n===\n***\n*@");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.DashSeparator);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.EqualsSeparator);
|
||||
Assert.DoesNotContain(result.Tokens, t => t.Type == TokenType.StarSeparator);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
171
Inspectron.Epson.Templates.Tests/Language/LexerTests.cs
Normal file
171
Inspectron.Epson.Templates.Tests/Language/LexerTests.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Language;
|
||||
|
||||
public class LexerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Tokenize_PlainText_ReturnsTextToken()
|
||||
{
|
||||
var lexer = new Lexer("Hello World");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == "Hello World");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_Binding_ReturnsBindingToken()
|
||||
{
|
||||
var lexer = new Lexer("{PropertyName}");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Binding && t.Value == "PropertyName");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_BindingWithFormat_ReturnsBindingTokenWithFormat()
|
||||
{
|
||||
var lexer = new Lexer("{TransactionDateTime:dd-MMM-yy}");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Binding && t.Value == "TransactionDateTime:dd-MMM-yy");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_StyleBlock_ReturnsStyleTokens()
|
||||
{
|
||||
var lexer = new Lexer("#bold,center# text #");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.StyleStart && t.Value == "bold,center");
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Text && t.Value == " text ");
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.StyleEnd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_DashSeparator_ReturnsSeparatorToken()
|
||||
{
|
||||
var lexer = new Lexer("---");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.DashSeparator);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_EqualsSeparator_ReturnsSeparatorToken()
|
||||
{
|
||||
var lexer = new Lexer("===");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.EqualsSeparator);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_StarSeparator_ReturnsSeparatorToken()
|
||||
{
|
||||
var lexer = new Lexer("***");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.StarSeparator);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_IfDirective_ReturnsIfToken()
|
||||
{
|
||||
var lexer = new Lexer("@if condition");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.If && t.Value == "condition");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_ForeachDirective_ReturnsForeachToken()
|
||||
{
|
||||
var lexer = new Lexer("@foreach item in items");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Foreach && t.Value == "item in items");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_EndDirective_ReturnsEndToken()
|
||||
{
|
||||
var lexer = new Lexer("@end");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_RowDirectives_ReturnsRowTokens()
|
||||
{
|
||||
var lexer = new Lexer("@row\n@endrow");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Row);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.EndRow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_Column_ReturnsColumnToken()
|
||||
{
|
||||
var lexer = new Lexer("|20|");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Column && t.Value.StartsWith("20"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_ColumnWithAlignment_ReturnsColumnTokenWithAlignment()
|
||||
{
|
||||
var lexer = new Lexer("|20,right|");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Column && t.Value == "20,right");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_MultipleLines_PreservesLineStructure()
|
||||
{
|
||||
var lexer = new Lexer("Line 1\nLine 2\nLine 3");
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var newlines = result.Tokens.Count(t => t.Type == TokenType.NewLine);
|
||||
Assert.Equal(3, newlines);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_ComplexTemplate_ParsesCorrectly()
|
||||
{
|
||||
var template = @"#bold,center# {Title} #
|
||||
---
|
||||
@if HasItems
|
||||
@foreach item in Items
|
||||
{item.Name}
|
||||
@end
|
||||
@end";
|
||||
var lexer = new Lexer(template);
|
||||
var result = lexer.Tokenize();
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.StyleStart);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.DashSeparator);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.If);
|
||||
Assert.Contains(result.Tokens, t => t.Type == TokenType.Foreach);
|
||||
Assert.Equal(2, result.Tokens.Count(t => t.Type == TokenType.End));
|
||||
}
|
||||
}
|
||||
155
Inspectron.Epson.Templates.Tests/Language/ParserTests.cs
Normal file
155
Inspectron.Epson.Templates.Tests/Language/ParserTests.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
using Inspectron.Epson.Templates.Language.Nodes;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Tests.Language;
|
||||
|
||||
public class ParserTests
|
||||
{
|
||||
private ParseResult Parse(string source)
|
||||
{
|
||||
var lexer = new Lexer(source);
|
||||
var lexerResult = lexer.Tokenize();
|
||||
var parser = new Parser(lexerResult.Tokens);
|
||||
return parser.Parse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_PlainText_ReturnsTextNode()
|
||||
{
|
||||
var result = Parse("Hello World");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Single(result.Template.ChildNodes.OfType<TextNode>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_Binding_ReturnsBindingNode()
|
||||
{
|
||||
var result = Parse("{Name}");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var binding = Assert.Single(result.Template.ChildNodes.OfType<BindingNode>());
|
||||
Assert.Equal("Name", binding.Path);
|
||||
Assert.Null(binding.Format);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BindingWithFormat_PreservesFormat()
|
||||
{
|
||||
var result = Parse("{Date:yyyy-MM-dd}");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var binding = Assert.Single(result.Template.ChildNodes.OfType<BindingNode>());
|
||||
Assert.Equal("Date", binding.Path);
|
||||
Assert.Equal("yyyy-MM-dd", binding.Format);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_StyledText_ReturnsStyledTextNode()
|
||||
{
|
||||
var result = Parse("#bold,center# text #");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var styled = Assert.Single(result.Template.ChildNodes.OfType<StyledTextNode>());
|
||||
Assert.Contains("bold", styled.Styles);
|
||||
Assert.Contains("center", styled.Styles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_DashSeparator_ReturnsSeparatorNode()
|
||||
{
|
||||
var result = Parse("---");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var separator = Assert.Single(result.Template.ChildNodes.OfType<SeparatorNode>());
|
||||
Assert.Equal(SeparatorStyle.Dash, separator.Style);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_IfBlock_ReturnsIfNode()
|
||||
{
|
||||
var result = Parse("@if condition\ntext\n@end");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var ifNode = Assert.Single(result.Template.ChildNodes.OfType<IfNode>());
|
||||
Assert.Equal("condition", ifNode.IfBranch.Condition);
|
||||
Assert.NotEmpty(ifNode.IfBranch.Body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_IfElseBlock_ReturnsIfNodeWithElseBranch()
|
||||
{
|
||||
var result = Parse("@if condition\nif-text\n@else\nelse-text\n@end");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var ifNode = Assert.Single(result.Template.ChildNodes.OfType<IfNode>());
|
||||
Assert.NotNull(ifNode.ElseBranch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_IfElseIfElseBlock_ReturnsAllBranches()
|
||||
{
|
||||
var result = Parse("@if cond1\ntext1\n@elseif cond2\ntext2\n@else\ntext3\n@end");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var ifNode = Assert.Single(result.Template.ChildNodes.OfType<IfNode>());
|
||||
Assert.Single(ifNode.ElseIfBranches);
|
||||
Assert.NotNull(ifNode.ElseBranch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ForeachBlock_ReturnsForeachNode()
|
||||
{
|
||||
var result = Parse("@foreach item in items\n{item}\n@end");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var foreach_ = Assert.Single(result.Template.ChildNodes.OfType<ForeachNode>());
|
||||
Assert.Equal("item", foreach_.ItemVariable);
|
||||
Assert.Equal("items", foreach_.CollectionPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_NestedBlocks_HandlesNestingCorrectly()
|
||||
{
|
||||
var result = Parse("@foreach item in items\n@if item.visible\n{item.name}\n@end\n@end");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var foreach_ = Assert.Single(result.Template.ChildNodes.OfType<ForeachNode>());
|
||||
Assert.Single(foreach_.Body.OfType<IfNode>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_RowWithColumns_ReturnsRowNode()
|
||||
{
|
||||
var result = Parse("@row\n|20|Name|10,right|Value\n@endrow");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
var row = Assert.Single(result.Template.ChildNodes.OfType<RowNode>());
|
||||
Assert.Equal(2, row.Columns.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_EmptyLine_ReturnsEmptyLineNode()
|
||||
{
|
||||
var result = Parse("line1\n\nline2");
|
||||
|
||||
Assert.False(result.HasErrors);
|
||||
Assert.Contains(result.Template.ChildNodes, n => n is EmptyLineNode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_UnclosedIfBlock_ReportsError()
|
||||
{
|
||||
var result = Parse("@if condition\ntext");
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_UnclosedForeachBlock_ReportsError()
|
||||
{
|
||||
var result = Parse("@foreach item in items\ntext");
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user