using Elsa.Expressions.JavaScript.Contracts; using Elsa.Expressions.Models; using Elsa.Testing.Shared; using Elsa.Workflows.LogPersistence; using Jint; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; namespace Elsa.JavaScript.IntegrationTests; /// /// Pins how .NET enums are exposed to JavaScript: a value reaches script as its member name, and so does a /// constant read off the registered enum type, so the two compare equal. /// public class EnumConversionTests { private readonly IServiceProvider _serviceProvider; private readonly IJavaScriptEvaluator _evaluator; public EnumConversionTests(ITestOutputHelper testOutputHelper) { _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build(); _evaluator = _serviceProvider.GetRequiredService(); } [Fact(DisplayName = "An enum value reaches a script as its member name")] public async Task EnumValuesAreExposedAsTheirName() { Assert.Equal("Include", await EvaluateAsync("return mode;", engine => engine.SetValue("mode", LogPersistenceMode.Include))); } [Fact(DisplayName = "An enum value reaches a script as a string")] public async Task EnumValuesAreStrings() { Assert.Equal("string", await EvaluateAsync("return typeof mode;", engine => engine.SetValue("mode", LogPersistenceMode.Include))); } [Fact(DisplayName = "A constant read off a registered enum type is its member name")] public async Task EnumConstantsAreExposedAsTheirName() { Assert.Equal("Include", await EvaluateAsync("return LogPersistenceMode.Include;")); } [Fact(DisplayName = "An enum value compares equal to the constant of the same member")] public async Task EnumValuesCompareEqualToTheirConstant() { // The two directions used to disagree: a value crossing the boundary became its name while a constant // read off the registered type stayed the underlying number, so this comparison was always false. Assert.Equal("true", await EvaluateAsync("return '' + (mode === LogPersistenceMode.Include);", engine => engine.SetValue("mode", LogPersistenceMode.Include))); } [Fact(DisplayName = "An enum-valued property of a .NET object reaches a script as its member name")] public async Task EnumMembersOfWrappedObjectsAreExposedAsTheirName() { Assert.Equal("Exclude", await EvaluateAsync("return holder.Mode;", engine => engine.SetValue("holder", new ModeHolder { Mode = LogPersistenceMode.Exclude }))); } [Fact(DisplayName = "A member name written back from a script converts to the enum value")] public async Task EnumMembersAcceptTheirNameOnTheWayBack() { var holder = new ModeHolder(); await EvaluateAsync("holder.Mode = 'Exclude'; return '';", engine => engine.SetValue("holder", holder)); Assert.Equal(LogPersistenceMode.Exclude, holder.Mode); } private async Task EvaluateAsync(string script, Action? configureEngine = null) { var context = new ExpressionExecutionContext(_serviceProvider, new()); return (string?)await _evaluator.EvaluateAsync(script, typeof(string), context, configureEngine: configureEngine); } private class ModeHolder { public LogPersistenceMode Mode { get; set; } } }