Files
2025-11-28 17:51:29 -05:00

438 lines
14 KiB
C#

// Copyright (c) MarketAlly LLC. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
using FluentAssertions;
using MarketAlly.IronWiki.Parsing;
using MarketAlly.IronWiki.Rendering;
using Xunit;
namespace MarketAlly.IronWiki.Tests;
public class TemplateExpanderTests
{
private readonly WikitextParser _parser = new();
[Fact]
public void Expand_SimpleTemplate_ReturnsContent()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Hello", "Hello, World!");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Hello}}");
var result = expander.Expand(doc);
result.Should().Contain("Hello, World!");
}
[Fact]
public void Expand_TemplateWithPositionalParameter_SubstitutesValue()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Greet", "Hello, {{{1}}}!");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Greet|Alice}}");
var result = expander.Expand(doc);
result.Should().Contain("Hello, Alice!");
}
[Fact]
public void Expand_TemplateWithNamedParameter_SubstitutesValue()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Greet", "Hello, {{{name}}}!");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Greet|name=Bob}}");
var result = expander.Expand(doc);
result.Should().Contain("Hello, Bob!");
}
[Fact]
public void Expand_TemplateWithDefaultValue_UsesDefaultWhenNotProvided()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Greet", "Hello, {{{1|World}}}!");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Greet}}");
var result = expander.Expand(doc);
result.Should().Contain("Hello, World!");
}
[Fact]
public void Expand_TemplateWithDefaultValue_OverridesWhenProvided()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Greet", "Hello, {{{1|World}}}!");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Greet|Universe}}");
var result = expander.Expand(doc);
result.Should().Contain("Hello, Universe!");
}
[Fact]
public void Expand_NestedTemplates_ExpandsRecursively()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Inner", "Inner Content");
provider.Add("Outer", "Start {{Inner}} End");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Outer}}");
var result = expander.Expand(doc);
result.Should().Contain("Start Inner Content End");
}
[Fact]
public void Expand_DeeplyNestedTemplates_ExpandsAllLevels()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Level3", "L3");
provider.Add("Level2", "L2-{{Level3}}-L2");
provider.Add("Level1", "L1-{{Level2}}-L1");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Level1}}");
var result = expander.Expand(doc);
result.Should().Contain("L1-L2-L3-L2-L1");
}
[Fact]
public void Expand_ParameterPassedToNestedTemplate_SubstitutesCorrectly()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Inner", "Hello, {{{1}}}!");
// Use a simpler pass-through pattern
provider.Add("Outer", "Prefix-{{Inner|Test}}-Suffix");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Outer}}");
var result = expander.Expand(doc);
result.Should().Contain("Prefix-Hello, Test!-Suffix");
}
[Fact]
public void Expand_CircularReference_DetectsAndStops()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("A", "{{B}}");
provider.Add("B", "{{A}}");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{A}}");
var result = expander.Expand(doc);
result.Should().Contain("loop detected");
}
[Fact]
public void Expand_RecursionLimit_StopsAtLimit()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Deep", "{{Deep}}"); // Self-recursive
var options = new TemplateExpanderOptions { MaxRecursionDepth = 5 };
var expander = new TemplateExpander(_parser, provider, options);
var doc = _parser.Parse("{{Deep}}");
var result = expander.Expand(doc);
// Self-recursive templates are detected as circular references (loop detected)
result.Should().Contain("loop detected");
}
[Fact]
public void Expand_UnknownTemplate_PreservesOriginal()
{
var provider = new DictionaryTemplateContentProvider();
var options = new TemplateExpanderOptions { PreserveUnknownTemplates = true };
var expander = new TemplateExpander(_parser, provider, options);
var doc = _parser.Parse("{{Unknown}}");
var result = expander.Expand(doc);
result.Should().Contain("{{Unknown}}");
}
[Fact]
public void Expand_MultipleTemplatesInDocument_ExpandsAll()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("A", "First");
provider.Add("B", "Second");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{A}} and {{B}}");
var result = expander.Expand(doc);
result.Should().Contain("First");
result.Should().Contain("Second");
}
[Fact]
public void Expand_TemplateWithWikitext_PreservesFormatting()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Formatted", "'''Bold''' and ''italic''");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Formatted}}");
var result = expander.Expand(doc);
result.Should().Contain("'''Bold'''");
result.Should().Contain("''italic''");
}
[Fact]
public void Expand_TemplateWithLinks_PreservesLinks()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Link", "See [[Article]] for more");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Link}}");
var result = expander.Expand(doc);
result.Should().Contain("[[Article]]");
}
[Fact]
public void Expand_IfParserFunction_TrueCondition()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{#if: yes | true | false}}");
var result = expander.Expand(doc);
result.Should().Contain("true");
result.Should().NotContain("false");
}
[Fact]
public void Expand_IfParserFunction_FalseCondition()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{#if: | true | false}}");
var result = expander.Expand(doc);
result.Should().Contain("false");
}
[Fact]
public void Expand_IfeqParserFunction_Equal()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{#ifeq: abc | abc | same | different}}");
var result = expander.Expand(doc);
result.Should().Contain("same");
}
[Fact]
public void Expand_IfeqParserFunction_NotEqual()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{#ifeq: abc | xyz | same | different}}");
var result = expander.Expand(doc);
result.Should().Contain("different");
}
[Fact]
public void Expand_SwitchParserFunction_MatchingCase()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{#switch: b | a=first | b=second | c=third}}");
var result = expander.Expand(doc);
result.Should().Contain("second");
}
[Fact]
public void Expand_SwitchParserFunction_DefaultCase()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{#switch: z | a=first | b=second | #default=default}}");
var result = expander.Expand(doc);
result.Should().Contain("default");
}
[Fact]
public void Expand_LcParserFunction_ConvertsToLowercase()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{lc:HELLO}}");
var result = expander.Expand(doc);
result.Should().Contain("hello");
}
[Fact]
public void Expand_UcParserFunction_ConvertsToUppercase()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{uc:hello}}");
var result = expander.Expand(doc);
result.Should().Contain("HELLO");
}
[Fact]
public void Expand_UcfirstParserFunction_CapitalizesFirst()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{ucfirst:hello world}}");
var result = expander.Expand(doc);
result.Should().Contain("Hello world");
}
[Fact]
public void Expand_LcfirstParserFunction_LowercasesFirst()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{lcfirst:HELLO}}");
var result = expander.Expand(doc);
result.Should().Contain("hELLO");
}
[Fact]
public void ExpandToDocument_ReturnsValidAst()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Test", "Simple content");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Test}}");
var expandedDoc = expander.ExpandToDocument(doc);
expandedDoc.Should().NotBeNull();
expandedDoc.Lines.Should().NotBeEmpty();
}
[Fact]
public async Task ExpandAsync_WorksCorrectly()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Async", "Async content");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Async}}");
var result = await expander.ExpandAsync(doc);
result.Should().Contain("Async content");
}
[Fact]
public void ExpandingTemplateResolver_ReturnsExpandedWikitext()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Bold", "'''Important'''");
var resolver = new ExpandingTemplateResolver(_parser, provider);
var renderer = new HtmlRenderer(templateResolver: resolver);
var doc = _parser.Parse("{{Bold}}");
var html = renderer.Render(doc);
// The resolver returns expanded wikitext, which is inserted as-is
// The '''Important''' is the expanded content from the template
html.Should().Contain("Important");
}
[Fact]
public void ExpandingTemplateResolver_ExpandsParameterizedTemplates()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Greet", "Hello, {{{1|World}}}!");
var resolver = new ExpandingTemplateResolver(_parser, provider);
var renderer = new HtmlRenderer(templateResolver: resolver);
var doc = _parser.Parse("{{Greet|Alice}}");
var html = renderer.Render(doc);
// The template should be expanded with the parameter
html.Should().Contain("Hello, Alice!");
}
[Fact]
public void ChainedTemplateContentProvider_TriesMultipleProviders()
{
var provider1 = new DictionaryTemplateContentProvider();
var provider2 = new DictionaryTemplateContentProvider();
provider2.Add("Found", "Found in second");
var chained = new ChainedTemplateContentProvider(provider1, provider2);
var expander = new TemplateExpander(_parser, chained);
var doc = _parser.Parse("{{Found}}");
var result = expander.Expand(doc);
result.Should().Contain("Found in second");
}
[Fact]
public void Expand_ComplexInfobox_ExpandsCorrectly()
{
var provider = new DictionaryTemplateContentProvider();
provider.Add("Infobox", @"{| class=""infobox""
|-
! colspan=""2"" | {{{title|No Title}}}
|-
| Type || {{{type|Unknown}}}
|-
| Value || {{{value|N/A}}}
|}");
var expander = new TemplateExpander(_parser, provider);
var doc = _parser.Parse("{{Infobox|title=Test Item|type=Widget|value=100}}");
var result = expander.Expand(doc);
result.Should().Contain("Test Item");
result.Should().Contain("Widget");
result.Should().Contain("100");
}
[Fact]
public void Expand_NullDocument_ThrowsArgumentNullException()
{
var provider = new DictionaryTemplateContentProvider();
var expander = new TemplateExpander(_parser, provider);
Action act = () => expander.Expand(null!);
act.Should().Throw<ArgumentNullException>();
}
}