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
14 changes: 13 additions & 1 deletion .github/workflows/CodeQuality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,23 @@ jobs:
fail-on-error: false

# Coverage comment: only once (ubuntu leg) and only on pull requests.
# The per-project test runs each emit their own coverage.cobertura.xml that also
# includes the shared production assemblies. Merge them into a single, de-duplicated
# report first so each assembly's coverage is the union across all suites (otherwise a
# shared assembly is counted once per file and its reported coverage is diluted).
- name: Merge coverage reports
if: matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request'
uses: danielpalme/ReportGenerator-GitHub-Action@ee3806a36b8b2eb9594cb3e5fae045af7e5ead10 # v5.5.6
with:
reports: TestResults/**/coverage.cobertura.xml
targetdir: CoverageReport
reporttypes: Cobertura

- name: Code coverage summary
if: matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request'
uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0
with:
filename: TestResults/**/coverage.cobertura.xml
filename: CoverageReport/Cobertura.xml
format: markdown
output: both

Expand Down
11 changes: 11 additions & 0 deletions ECoreNetto.Extensions.Tests/ModelElementExtensionsTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,16 @@ public void Verify_that_RemoveUnwantedHtmlTags_returns_expected_results()
var unchanged = ModelElementExtensions.RemoveUnwantedHtmlTags(html, new List<string>());
Assert.That(unchanged, Is.EqualTo(html));
}

[Test]
public void Verify_that_RemoveUnwantedHtmlTags_returns_input_when_there_are_no_element_or_text_nodes()
{
// a comment-only fragment yields no element/text nodes, so the input is returned unchanged
const string html = "<!-- just a comment -->";

var result = ModelElementExtensions.RemoveUnwantedHtmlTags(html, new List<string> { "p" });

Assert.That(result, Is.EqualTo(html));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,51 @@ public void Verify_that_QueryIsNullable_returns_expected_results()
var ingredientsFeature = recipeClass.EStructuralFeatures.Single(x => x.Name == "ingredients");
Assert.That(ingredientsFeature.QueryIsNullable(), Is.False);
}

[Test]
public void Verify_that_QueryStructuralFeatureNameEqualsEnclosingType_returns_expected_results()
{
var amountClass = this.rootPackage.EClassifiers.OfType<EClass>().Single(x => x.Name == "Amount");

var amountFeature = amountClass.EStructuralFeatures.Single(x => x.Name == "amount");
Assert.That(amountFeature.QueryStructuralFeatureNameEqualsEnclosingType(amountClass), Is.True);

var unitFeature = amountClass.EStructuralFeatures.Single(x => x.Name == "unit");
Assert.That(unitFeature.QueryStructuralFeatureNameEqualsEnclosingType(amountClass), Is.False);
}

[Test]
public void Verify_that_QueryStructuralFeatureNameEqualsEnclosingType_throws_when_class_is_null()
{
var amountClass = this.rootPackage.EClassifiers.OfType<EClass>().Single(x => x.Name == "Amount");
var amountFeature = amountClass.EStructuralFeatures.Single(x => x.Name == "amount");

EClass nullClass = null!;

Assert.That(() => amountFeature.QueryStructuralFeatureNameEqualsEnclosingType(nullClass),
Throws.ArgumentNullException);
}

[Test]
public void Verify_that_the_extension_methods_throw_when_the_structural_feature_is_null()
{
EStructuralFeature feature = null!;

var eClass = this.rootPackage.EClassifiers.OfType<EClass>().First();

Assert.Multiple(() =>
{
Assert.That(() => feature.QueryIsEnum(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryClass(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryIsEnumerable(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryIsAttribute(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryIsReference(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryIsContainment(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryHasDefaultValue(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryTypeName(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryIsNullable(), Throws.ArgumentNullException);
Assert.That(() => feature.QueryStructuralFeatureNameEqualsEnclosingType(eClass), Throws.ArgumentNullException);
});
}
}
}
10 changes: 10 additions & 0 deletions ECoreNetto.HandleBars.Tests/BooleanHelperTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,15 @@ public void Verify_that_ToLowerCase_returns_expected_result()

Assert.That(falseResult, Is.EqualTo("false"));
}

[Test]
public void Verify_that_ToLowerCase_throws_when_not_exactly_one_argument()
{
var template = "{{ #Boolean.ToLowerCase this that }}";

var action = this.handlebarsContenxt.Compile(template);

Assert.Throws<HandlebarsException>(() => action(true));
}
}
}
95 changes: 95 additions & 0 deletions ECoreNetto.HandleBars.Tests/DocumentationHelperTestFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// -------------------------------------------------------------------------------------------------
// <copyright file="DocumentationHelperTestFixture.cs" company="Starion Group S.A.">
//
// Copyright 2017-2025 Starion Group S.A.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// </copyright>
// ------------------------------------------------------------------------------------------------

namespace ECoreNetto.HandleBars.Tests
{
using System;
using System.Globalization;
using System.IO;
using System.Linq;

using ECoreNetto;

using HandlebarsDotNet;

using NUnit.Framework;

/// <summary>
/// Suite of tests for the <see cref="DocumentationHelper"/> class
/// </summary>
[TestFixture]
public class DocumentationHelperTestFixture
{
private IHandlebars handlebarsContenxt = null!;

private EPackage root = null!;

[SetUp]
public void SetUp()
{
this.handlebarsContenxt = Handlebars.Create();
this.handlebarsContenxt.Configuration.FormatProvider = CultureInfo.InvariantCulture;

DocumentationHelper.RegisteredDocumentationHelper(this.handlebarsContenxt);

var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "recipe.ecore");

this.root = ModelLoader.Load(path);
}

[Test]
public void Verify_that_RawDocumentation_returns_the_documentation_when_present()
{
var template = "{{ #RawDocumentation this }}";

var action = this.handlebarsContenxt.Compile(template);

var eClass = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Ingredient");

var result = action(eClass);

Assert.That(result, Does.Contain("Any of the foods or substances"));
}

[Test]
public void Verify_that_RawDocumentation_returns_placeholder_when_no_documentation_is_present()
{
var template = "{{ #RawDocumentation this }}";

var action = this.handlebarsContenxt.Compile(template);

var eClass = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Recipe");

var result = action(eClass);

Assert.That(result, Is.EqualTo("No Documentation Provided"));
}

[Test]
public void Verify_that_RawDocumentation_throws_when_context_is_not_an_EModelElement()
{
var template = "{{ #RawDocumentation this }}";

var action = this.handlebarsContenxt.Compile(template);

Assert.Throws<ArgumentException>(() => action("not-an-emodelelement"));
}
}
}
34 changes: 34 additions & 0 deletions ECoreNetto.HandleBars.Tests/GeneralizationHelperTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,39 @@ public void Verify_that_GeneralizationClasses_returns_interface_for_class_withou

Assert.That(result, Is.EqualTo(": IRelation"));
}

[Test]
public void Verify_that_GeneralizationInterfaces_returns_empty_for_class_without_supertypes()
{
var template = "{{ #Generalization.Interfaces this }}";

var action = this.handlebarsContenxt.Compile(template);

var eClass = this.root.EClassifiers.Single(x => x.Name == "Relation");

var result = action(eClass);

Assert.That(result, Is.Empty);
}

[Test]
public void Verify_that_GeneralizationInterfaces_throws_when_context_is_not_an_EClass()
{
var template = "{{ #Generalization.Interfaces this }}";

var action = this.handlebarsContenxt.Compile(template);

Assert.That(() => action("not-an-eclass"), Throws.ArgumentException);
}

[Test]
public void Verify_that_GeneralizationClasses_throws_when_context_is_not_an_EClass()
{
var template = "{{ #Generalization.Classes this }}";

var action = this.handlebarsContenxt.Compile(template);

Assert.That(() => action("not-an-eclass"), Throws.ArgumentException);
}
}
}
120 changes: 120 additions & 0 deletions ECoreNetto.HandleBars.Tests/StructuralFeatureHelperTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,125 @@ public void Verify_that_StructuralFeature_NameEqualsEnclosingType_throws_when_no

Assert.Throws<HandlebarsException>(() => action(new { feature = eStructuralFeature }));
}

[Test]
public void Verify_that_StructuralFeature_IsEnumerable_block_renders_only_for_enumerable_feature()
{
var template = "{{#StructuralFeature.IsEnumerable this}}ENUMERABLE{{/StructuralFeature.IsEnumerable}}";
var action = this.handlebarsContenxt.Compile(template);

var ingredients = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Recipe")
.EStructuralFeatures.Single(x => x.Name == "ingredients");
Assert.That(action(ingredients), Is.EqualTo("ENUMERABLE"));

var minutes = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "TimeTrigger")
.EStructuralFeatures.Single(x => x.Name == "minutes");
Assert.That(action(minutes), Is.Empty);
}

[Test]
public void Verify_that_StructuralFeature_IsAttribute_block_renders_only_for_attribute()
{
var template = "{{#StructuralFeature.IsAttribute this}}ATTRIBUTE{{/StructuralFeature.IsAttribute}}";
var action = this.handlebarsContenxt.Compile(template);

var minutes = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "TimeTrigger")
.EStructuralFeatures.Single(x => x.Name == "minutes");
Assert.That(action(minutes), Is.EqualTo("ATTRIBUTE"));

var ingredients = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Recipe")
.EStructuralFeatures.Single(x => x.Name == "ingredients");
Assert.That(action(ingredients), Is.Empty);
}

[Test]
public void Verify_that_StructuralFeature_IsReference_block_renders_only_for_reference()
{
var template = "{{#StructuralFeature.IsReference this}}REFERENCE{{/StructuralFeature.IsReference}}";
var action = this.handlebarsContenxt.Compile(template);

var ingredients = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Recipe")
.EStructuralFeatures.Single(x => x.Name == "ingredients");
Assert.That(action(ingredients), Is.EqualTo("REFERENCE"));

var minutes = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "TimeTrigger")
.EStructuralFeatures.Single(x => x.Name == "minutes");
Assert.That(action(minutes), Is.Empty);
}

[Test]
public void Verify_that_StructuralFeature_IsEnum_block_renders_only_for_enum_attribute()
{
var template = "{{#StructuralFeature.IsEnum this}}ENUM{{/StructuralFeature.IsEnum}}";
var action = this.handlebarsContenxt.Compile(template);

var unit = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Amount")
.EStructuralFeatures.Single(x => x.Name == "unit");
Assert.That(action(unit), Is.EqualTo("ENUM"));

var ingredients = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Recipe")
.EStructuralFeatures.Single(x => x.Name == "ingredients");
Assert.That(action(ingredients), Is.Empty);
}

[Test]
public void Verify_that_StructuralFeature_QueryStructuralFeatureNameEqualsEnclosingType_returns_expected_result()
{
var template = "{{ #StructuralFeature.QueryStructuralFeatureNameEqualsEnclosingType feature eClass }}";
var action = this.handlebarsContenxt.Compile(template);

var eClass = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Amount");

var amount = eClass.EStructuralFeatures.Single(x => x.Name == "amount");
Assert.That(action(new { feature = amount, eClass }), Is.EqualTo("True"));

var unit = eClass.EStructuralFeatures.Single(x => x.Name == "unit");
Assert.That(action(new { feature = unit, eClass }), Is.EqualTo("False"));
}

[Test]
public void Verify_that_StructuralFeature_QueryTypeName_returns_expected_result()
{
var template = "{{ #StructuralFeature.QueryTypeName this }}";
var action = this.handlebarsContenxt.Compile(template);

var ingredients = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Recipe")
.EStructuralFeatures.Single(x => x.Name == "ingredients");

Assert.That(action(ingredients), Is.EqualTo("Ingredient"));
}

[Test]
public void Verify_that_StructuralFeature_QueryTypeName_throws_when_context_is_not_a_structural_feature()
{
var template = "{{ #StructuralFeature.QueryTypeName this }}";
var action = this.handlebarsContenxt.Compile(template);

Assert.That(() => action("not-a-structural-feature"), Throws.ArgumentException);
}

[TestCase("StructuralFeature.QueryIsEnumerable", false)]
[TestCase("StructuralFeature.QueryIsAttribute", false)]
[TestCase("StructuralFeature.QueryIsReference", false)]
[TestCase("StructuralFeature.QueryIsEnum", false)]
[TestCase("StructuralFeature.QueryHasDefaultValue", false)]
[TestCase("StructuralFeature.QueryIsContainment", false)]
[TestCase("StructuralFeature.IsEnumerable", true)]
[TestCase("StructuralFeature.IsAttribute", true)]
[TestCase("StructuralFeature.IsReference", true)]
[TestCase("StructuralFeature.IsEnum", true)]
public void Verify_that_single_argument_helpers_throw_when_not_exactly_one_argument(string helper, bool isBlock)
{
var template = isBlock
? "{{#" + helper + " this that}}X{{/" + helper + "}}"
: "{{ #" + helper + " this that }}";

var action = this.handlebarsContenxt.Compile(template);

var ingredients = this.root.EClassifiers.OfType<EClass>().Single(x => x.Name == "Recipe")
.EStructuralFeatures.Single(x => x.Name == "ingredients");

Assert.Throws<HandlebarsException>(() => action(ingredients));
}
}
}
Loading