Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more test cases for MtTransformer #47

Merged
merged 6 commits into from
Feb 4, 2024
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
16 changes: 16 additions & 0 deletions src/ManiaTemplates/Exceptions/CurlyBraceCountMismatchException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace ManiaTemplates.Exceptions;

public class CurlyBraceCountMismatchException : Exception
{
public CurlyBraceCountMismatchException()
{
}

public CurlyBraceCountMismatchException(string message) : base(message)
{
}

public CurlyBraceCountMismatchException(string message, Exception inner) : base(message, inner)
{
}
}
16 changes: 16 additions & 0 deletions src/ManiaTemplates/Exceptions/EmptyNodeAttributeException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace ManiaTemplates.Exceptions;

public class EmptyNodeAttributeException : Exception
{
public EmptyNodeAttributeException()
{
}

public EmptyNodeAttributeException(string message) : base(message)
{
}

public EmptyNodeAttributeException(string message, Exception inner) : base(message, inner)
{
}
}
16 changes: 16 additions & 0 deletions src/ManiaTemplates/Exceptions/InterpolationRecursionException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace ManiaTemplates.Exceptions;

public class InterpolationRecursionException : Exception
{
public InterpolationRecursionException()
{
}

public InterpolationRecursionException(string message) : base(message)
{
}

public InterpolationRecursionException(string message, Exception inner) : base(message, inner)
{
}
}
109 changes: 87 additions & 22 deletions src/ManiaTemplates/Lib/MtTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ public string BuildManialink(MtComponent rootComponent, string className, int ve
var body = ProcessNode(
XmlStringToNode(rootComponent.TemplateContent),
_engine.BaseMtComponents.Overload(rootComponent.ImportedComponents),
rootContext
rootContext,
parentComponent: rootComponent,
isRootNode: true
);

var template = new Snippet
Expand Down Expand Up @@ -86,7 +88,7 @@ public string BuildManialink(MtComponent rootComponent, string className, int ve
/// </summary>
private string CreateDoNothingMethod()
{
return _maniaTemplateLanguage.FeatureBlock(@"string DoNothing(){return """";}").ToString();
return _maniaTemplateLanguage.FeatureBlock("""string DoNothing(){ return ""; }""").ToString();
}

/// <summary>
Expand Down Expand Up @@ -119,7 +121,7 @@ private string CreateBodyRenderMethod(string body, MtDataContext context, MtComp
bodyRenderMethod.AppendLine($"var __data = {renderBodyArguments};");

//Root mania script block
string rootScriptBlock = "";
var rootScriptBlock = "";
if (rootComponent.Scripts.Count > 0)
{
rootScriptBlock = CreateManiaScriptBlock(rootComponent);
Expand Down Expand Up @@ -234,12 +236,12 @@ private string CreateSlotRenderMethod(MtComponent component, int scope, MtDataCo

if (context.ParentContext != null)
{
output.AppendLine(CreateLocalVariablesFromContext(context.ParentContext));
output.AppendLine(CreateLocalVariablesFromContext(context.ParentContext, parentComponent?.Properties.Keys));
variablesInherited.AddRange(context.ParentContext.Keys);
}

output
.AppendLine(CreateLocalVariablesFromContext(context, variablesInherited))
.AppendLine(CreateLocalVariablesFromContext(context, parentComponent?.Properties.Keys))
.AppendLine(_maniaTemplateLanguage.FeatureBlockEnd())
.AppendLine(slotContent)
.AppendLine(_maniaTemplateLanguage.FeatureBlockStart())
Expand Down Expand Up @@ -270,7 +272,7 @@ private static string CreateLocalVariablesFromContext(MtDataContext context,
/// Process a ManiaTemplate node.
/// </summary>
private string ProcessNode(XmlNode node, MtComponentMap availableMtComponents, MtDataContext context,
MtComponent? parentComponent = null)
MtComponent? parentComponent = null, bool isRootNode = false)
{
Snippet snippet = new();

Expand Down Expand Up @@ -312,7 +314,8 @@ private string ProcessNode(XmlNode node, MtComponentMap availableMtComponents, M
component
),
slotContents,
parentComponent
parentComponent,
isRootNode
);

subSnippet.AppendLine(_maniaTemplateLanguage.FeatureBlockStart())
Expand Down Expand Up @@ -345,7 +348,8 @@ private string ProcessNode(XmlNode node, MtComponentMap availableMtComponents, M
if (hasChildren)
{
subSnippet.AppendLine(1,
ProcessNode(childNode, availableMtComponents, currentContext));
ProcessNode(childNode, availableMtComponents, currentContext,
parentComponent: parentComponent));
subSnippet.AppendLine(CreateXmlClosingTag(tag));
}

Expand Down Expand Up @@ -384,8 +388,15 @@ private Dictionary<string, string> GetSlotContentsBySlotName(XmlNode componentNo
continue;
}

//Get the name from the slot attribute, or default if not found.
var slotName = childNode.Attributes?["slot"]?.Value.ToLower() ?? "default";

if (slotName.Trim().Length == 0)
{
throw new EmptyNodeAttributeException(
$"There's a template tag with empty slot name in <{componentNode.Name}></>.");
}

if (slotName == "default")
{
//Do not strip contents for default slot from node
Expand Down Expand Up @@ -420,7 +431,8 @@ private string ProcessComponentNode(
MtComponentAttributes attributeList,
string componentBody,
IReadOnlyDictionary<string, string> slotContents,
MtComponent? parentComponent = null
MtComponent? parentComponent = null,
bool isRootNode = false
)
{
foreach (var slotName in component.Slots)
Expand Down Expand Up @@ -527,7 +539,7 @@ private string ProcessComponentNode(
renderComponentCall.Append(
$", __slotRenderer_{parentSlotName}: __slotRenderer_{parentSlotName}");
}

foreach (var propertyName in parentComponent.Properties.Keys)
{
renderComponentCall.Append($",{propertyName}: {propertyName}");
Expand All @@ -537,11 +549,10 @@ private string ProcessComponentNode(
{
foreach (var parentSlotName in component.Slots)
{
renderComponentCall.Append(
$", __slotRenderer_{parentSlotName}: () => DoNothing()");
renderComponentCall.Append($", __slotRenderer_{parentSlotName}: () => DoNothing()");
}
}

renderComponentCall.Append(')');

i++;
Expand All @@ -554,7 +565,7 @@ private string ProcessComponentNode(

return renderComponentCall.ToString();
}

/// <summary>
/// Creates the method which renders the contents of a component.
/// </summary>
Expand Down Expand Up @@ -775,15 +786,15 @@ private string CreateManiaScriptDirectivesBlock()
}

/// <summary>
/// Checks the attribute list for if-condition and returns it, else null.
/// Checks the attribute list for if-condition and if found removes & returns it, else null.
/// </summary>
private string? GetIfConditionFromNodeAttributes(MtComponentAttributes attributeList)
{
return attributeList.ContainsKey("if") ? attributeList.Pull("if") : null;
}

/// <summary>
/// Checks the attribute list for name and returns it, else "default".
/// Checks the attribute list for name and if found removes & returns it, else "default".
/// </summary>
private string GetNameFromNodeAttributes(MtComponentAttributes attributeList)
{
Expand Down Expand Up @@ -922,7 +933,7 @@ private static string CreateContextClassProperties(MtDataContext context)
/// <summary>
/// Parses the attributes of a XmlNode to an MtComponentAttributes-instance.
/// </summary>
private static MtComponentAttributes GetXmlNodeAttributes(XmlNode node)
public static MtComponentAttributes GetXmlNodeAttributes(XmlNode node)
{
var attributeList = new MtComponentAttributes();
if (node.Attributes == null) return attributeList;
Expand All @@ -940,6 +951,8 @@ private static MtComponentAttributes GetXmlNodeAttributes(XmlNode node)
/// </summary>
private string BuildManiaScripts(MtComponent rootComponent)
{
//TODO: check if method can be removed

var maniaScripts = rootComponent.Scripts.ToDictionary(script => script.ContentHash());
foreach (var (key, value) in _maniaScripts)
{
Expand Down Expand Up @@ -1005,7 +1018,7 @@ private static MtDataContext GetContextFromComponent(MtComponent component, stri
/// Returns C# code representation of the type.
/// </summary>
/// <param name="type">The type.</param>
private static string GetFormattedName(Type type)
public static string GetFormattedName(Type type)
{
if (type.IsSubclassOf(typeof(DynamicObject)))
{
Expand Down Expand Up @@ -1081,7 +1094,7 @@ public static string WrapStringInQuotes(string str)
/// name = Shown in in-game debugger.
/// version = Version for the markup language of Trackmania.
/// </summary>
private static string ManiaLinkStart(string name, int version = 3, string? displayLayer = null)
public static string ManiaLinkStart(string name, int version = 3, string? displayLayer = null)
{
var layer = "";
if (displayLayer != null)
Expand All @@ -1103,7 +1116,7 @@ private static string ManiaLinkEnd()
/// <summary>
/// Creates a xml opening tag for the given string and attribute list.
/// </summary>
private string CreateXmlOpeningTag(string tag, MtComponentAttributes attributeList, bool hasChildren)
public string CreateXmlOpeningTag(string tag, MtComponentAttributes attributeList, bool hasChildren)
{
var output = $"<{tag}";

Expand Down Expand Up @@ -1132,7 +1145,7 @@ private static string CreateXmlClosingTag(string tag)
/// <summary>
/// Converts any valid XML-string into an XmlNode-element.
/// </summary>
private static XmlNode XmlStringToNode(string content)
public static XmlNode XmlStringToNode(string content)
{
var doc = new XmlDocument();
doc.LoadXml($"<doc>{content}</doc>");
Expand All @@ -1145,6 +1158,9 @@ private static XmlNode XmlStringToNode(string content)
/// </summary>
public static string ReplaceCurlyBraces(string value, Func<string, string> curlyContentWrapper)
{
CheckForCurlyBraceCountMismatch(value);
CheckInterpolationRecursion(value);

var matches = TemplateInterpolationRegex.Match(value);
var output = value;

Expand All @@ -1161,10 +1177,59 @@ public static string ReplaceCurlyBraces(string value, Func<string, string> curly
return output;
}

/// <summary>
/// Checks whether double interpolation exists ({{ {{ a }} {{ b }} }}) and throws exception if so.
/// </summary>
public static void CheckInterpolationRecursion(string value)
{
var openCurlyBraces = 0;
foreach (var character in value.ToCharArray())
{
if (character == '{')
{
openCurlyBraces++;

if (openCurlyBraces >= 4)
{
throw new InterpolationRecursionException(
$"Double interpolation found in: {value}. You must not use double curly braces inside other double curly braces.");
}
}
else if (character == '}')
{
openCurlyBraces--;
}
}
}

/// <summary>
/// Checks whether double interpolation exists ({{ {{ a }} {{ b }} }}) and throws exception if so.
/// </summary>
public static void CheckForCurlyBraceCountMismatch(string value)
{
var openCurlyBraces = 0;
foreach (var character in value.ToCharArray())
{
if (character == '{')
{
openCurlyBraces++;
}
else if (character == '}')
{
openCurlyBraces--;
}
}

if (openCurlyBraces != 0)
{
throw new CurlyBraceCountMismatchException($"Found curly brace count mismatch in: {value}.");
}
}

/// <summary>
/// Joins consecutive feature blocks to reduce generated code.
/// </summary>
private static string JoinFeatureBlocks(string manialink)
public static string JoinFeatureBlocks(string manialink)
{
var match = TemplateFeatureControlRegex.Match(manialink);
var output = new Snippet();
Expand Down
36 changes: 28 additions & 8 deletions tests/ManiaTemplates.Tests/IntegrationTests/ManialinkEngineTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,20 @@ await Assert.ThrowsAsync<DuplicateSlotException>(() =>
[Fact]
public async void Should_Render_Component_Without_Content_For_Slot()
{
var slotRecursionOuterTwoTemplate = await File.ReadAllTextAsync("IntegrationTests/templates/slot-recursion-outer-two.mt");
var slotRecursionOuterTemplate = await File.ReadAllTextAsync("IntegrationTests/templates/slot-recursion-outer.mt");
var slotRecursionInnerTemplate = await File.ReadAllTextAsync("IntegrationTests/templates/slot-recursion-inner.mt");
var slotRecursionOuterTwoTemplate =
await File.ReadAllTextAsync("IntegrationTests/templates/slot-recursion-outer-two.mt");
var slotRecursionOuterTemplate =
await File.ReadAllTextAsync("IntegrationTests/templates/slot-recursion-outer.mt");
var slotRecursionInnerTemplate =
await File.ReadAllTextAsync("IntegrationTests/templates/slot-recursion-inner.mt");
var expected = await File.ReadAllTextAsync("IntegrationTests/expected/single-slot-unfilled.xml");
var assemblies = new[] { typeof(ManiaTemplateEngine).Assembly, typeof(ComplexDataType).Assembly };

_maniaTemplateEngine.AddTemplateFromString("SlotRecursionOuterTwo", slotRecursionOuterTwoTemplate);
_maniaTemplateEngine.AddTemplateFromString("SlotRecursionOuter", slotRecursionOuterTemplate);
_maniaTemplateEngine.AddTemplateFromString("SlotRecursionInner", slotRecursionInnerTemplate);
var template = _maniaTemplateEngine.RenderAsync("SlotRecursionInner", new{}, assemblies).Result;

var template = _maniaTemplateEngine.RenderAsync("SlotRecursionInner", new { }, assemblies).Result;
Assert.Equal(expected, template, ignoreLineEndingDifferences: true);
}

Expand All @@ -110,15 +113,32 @@ public async void Should_Pass_Properties_To_Components_And_Slots()
var testComponentTemplate = await File.ReadAllTextAsync("IntegrationTests/templates/component.mt");
var expected = await File.ReadAllTextAsync("IntegrationTests/expected/property-test.xml");
var assemblies = new[] { typeof(ManiaTemplateEngine).Assembly, typeof(ComplexDataType).Assembly };

_maniaTemplateEngine.AddTemplateFromString("PropertyTest", propertyTestTemplate);
_maniaTemplateEngine.AddTemplateFromString("Wrapper", testWrapperTemplate);
_maniaTemplateEngine.AddTemplateFromString("TestComponent", testComponentTemplate);

var template = _maniaTemplateEngine.RenderAsync("PropertyTest", new
{
testVariable = "integration"
}, assemblies).Result;
Assert.Equal(expected, template, ignoreLineEndingDifferences: true);
}

[Fact]
public async void Should_Fill_All_Slots()
{
var baseTemplate = await File.ReadAllTextAsync("IntegrationTests/templates/slots/base.mt");
var containerTemplate = await File.ReadAllTextAsync("IntegrationTests/templates/slots/container.mt");
var windowTemplate = await File.ReadAllTextAsync("IntegrationTests/templates/slots/window.mt");
var expected = await File.ReadAllTextAsync("IntegrationTests/expected/slots/manialink.xml");
var assemblies = new[] { typeof(ManiaTemplateEngine).Assembly, typeof(ComplexDataType).Assembly };

_maniaTemplateEngine.AddTemplateFromString("Base", baseTemplate);
_maniaTemplateEngine.AddTemplateFromString("Container", containerTemplate);
_maniaTemplateEngine.AddTemplateFromString("Window", windowTemplate);

var template = _maniaTemplateEngine.RenderAsync("Base", new { }, assemblies).Result;
Assert.Equal(expected, template, ignoreLineEndingDifferences: true);
}
}
Loading