diff --git a/src/modules/Elsa.Expressions.JavaScript/Helpers/ObjectArrayHelper.cs b/src/modules/Elsa.Expressions.JavaScript/Helpers/ObjectArrayHelper.cs
index 4ad175950..7593211c2 100644
--- a/src/modules/Elsa.Expressions.JavaScript/Helpers/ObjectArrayHelper.cs
+++ b/src/modules/Elsa.Expressions.JavaScript/Helpers/ObjectArrayHelper.cs
@@ -5,6 +5,7 @@ namespace Elsa.Expressions.JavaScript.Helpers;
///
/// Contains helper methods for working with object arrays.
///
+[Obsolete("Jint decides array-likeness itself and attaches Array.prototype to array-like wrappers when Options.Interop.AttachArrayPrototype is enabled (the default). This helper is no longer used and will be removed in a future version.")]
public static class ObjectArrayHelper
{
///
diff --git a/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs
index 1fa60013d..ad7fa1bed 100644
--- a/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs
+++ b/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs
@@ -4,13 +4,11 @@ using Acornima.Ast;
using Elsa.Expressions.Helpers;
using Elsa.Expressions.Models;
using Elsa.Expressions.JavaScript.Contracts;
-using Elsa.Expressions.JavaScript.Helpers;
using Elsa.Expressions.JavaScript.Notifications;
using Elsa.Expressions.JavaScript.ObjectConverters;
using Elsa.Expressions.JavaScript.Options;
using Elsa.Mediator.Contracts;
using Jint;
-using Jint.Runtime.Interop;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
@@ -70,7 +68,6 @@ public class JintJavaScriptEvaluator(IConfiguration configuration, INotification
engineOptions.Interop.EnumConversion = EnumConversionMode.String;
ConfigureClrAccess(engineOptions);
- ConfigureObjectWrapper(engineOptions);
ConfigureObjectConverters(engineOptions);
ConfigureExecutionConstraints(engineOptions, cancellationToken);
@@ -93,19 +90,6 @@ public class JintJavaScriptEvaluator(IConfiguration configuration, INotification
options.AllowClr();
}
- private void ConfigureObjectWrapper(Jint.Options options)
- {
- options.SetWrapObjectHandler((engine, target, type) =>
- {
- var instance = ObjectWrapper.Create(engine, target);
-
- if (ObjectArrayHelper.DetermineIfObjectIsArrayLikeClrCollection(target.GetType()))
- instance.Prototype = engine.Intrinsics.Array.PrototypeObject;
-
- return instance;
- });
- }
-
private void ConfigureExecutionConstraints(Jint.Options options, CancellationToken cancellationToken)
{
// An expression that never returns would otherwise occupy the calling thread forever.
diff --git a/test/integration/Elsa.JavaScript.IntegrationTests/ObjectWrappingTests.cs b/test/integration/Elsa.JavaScript.IntegrationTests/ObjectWrappingTests.cs
new file mode 100644
index 000000000..0ff4d2c15
--- /dev/null
+++ b/test/integration/Elsa.JavaScript.IntegrationTests/ObjectWrappingTests.cs
@@ -0,0 +1,79 @@
+using System.Dynamic;
+using Elsa.Expressions.JavaScript.Contracts;
+using Elsa.Expressions.Models;
+using Elsa.Testing.Shared;
+using Jint;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Elsa.JavaScript.IntegrationTests;
+
+///
+/// Verifies how CLR objects are exposed to JavaScript: array-like collections should behave like arrays,
+/// while dictionary-like objects (such as the variables and args containers) should behave
+/// like plain objects.
+///
+public class ObjectWrappingTests
+{
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IJavaScriptEvaluator _evaluator;
+
+ public ObjectWrappingTests(ITestOutputHelper testOutputHelper)
+ {
+ _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
+ _evaluator = _serviceProvider.GetRequiredService();
+ }
+
+ [Fact(DisplayName = "The variables container is a plain object, not an array")]
+ public async Task VariablesContainerIsNotArrayLike()
+ {
+ Assert.Equal("false", await EvaluateAsync("return '' + (Object.getPrototypeOf(variables) === Array.prototype);"));
+ Assert.Equal("undefined", await EvaluateAsync("return typeof variables.map;"));
+ Assert.Equal("undefined", await EvaluateAsync("return typeof variables.filter;"));
+ Assert.Equal("undefined", await EvaluateAsync("return typeof variables.length;"));
+ }
+
+ [Fact(DisplayName = "A dictionary-like object is a plain object, not an array")]
+ public async Task DictionaryLikeObjectsAreNotArrayLike()
+ {
+ var expando = (IDictionary)new ExpandoObject();
+ expando["greeting"] = "hello";
+
+ Assert.Equal("undefined", await EvaluateAsync("return typeof subject.map;", engine => engine.SetValue("subject", expando)));
+ Assert.Equal("hello", await EvaluateAsync("return subject.greeting;", engine => engine.SetValue("subject", expando)));
+ Assert.Equal("undefined", await EvaluateAsync("return typeof subject.map;", engine => engine.SetValue("subject", new Dictionary { ["greeting"] = "hello" })));
+ }
+
+ [Theory(DisplayName = "Array-like CLR collections expose the array prototype")]
+ [InlineData("list")]
+ [InlineData("set")]
+ [InlineData("array")]
+ public async Task ArrayLikeCollectionsExposeArrayPrototype(string name)
+ {
+ Assert.Equal("function", await EvaluateAsync($"return typeof {name}.map;", ConfigureCollections));
+ Assert.Equal("true", await EvaluateAsync($"return '' + (Object.getPrototypeOf({name}) === Array.prototype);", ConfigureCollections));
+ }
+
+ [Theory(DisplayName = "Indexable CLR collections support array iteration methods")]
+ [InlineData("list")]
+ [InlineData("array")]
+ public async Task IndexableCollectionsSupportArrayMethods(string name)
+ {
+ Assert.Equal("2,4,6", await EvaluateAsync($"return {name}.map(x => x * 2).join(',');", ConfigureCollections));
+ Assert.Equal("6", await EvaluateAsync($"return '' + {name}.reduce((a, b) => a + b, 0);", ConfigureCollections));
+ }
+
+ private static void ConfigureCollections(Engine engine)
+ {
+ engine.SetValue("list", new List { 1, 2, 3 });
+ engine.SetValue("set", new HashSet { 1, 2, 3 });
+ engine.SetValue("array", new[] { 1, 2, 3 });
+ }
+
+ private async Task EvaluateAsync(string script, Action? configureEngine = null)
+ {
+ var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new());
+ return (T?)await _evaluator.EvaluateAsync(script, typeof(T), expressionExecutionContext, configureEngine: configureEngine);
+ }
+}