Skip to content

Commit

Permalink
fix JS failing on callback/function parameter + add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
luttje committed Nov 3, 2023
1 parent cc9ce13 commit 606db1b
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 53 deletions.
24 changes: 5 additions & 19 deletions Core/Key2Joy.Core/Mapping/Actions/Logic/SetIntervalAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,13 @@ public SetIntervalAction(string name)
/// Repeatedly calls a function or executes a code snippet, with a fixed time delay between each call
/// </summary>
/// <markdown-example>
/// Shows how to count up to 10 every second and then stop by using ClearInterval();
/// Shows how to count every second
/// <code language="js">
/// <![CDATA[
/// setTimeout(function () {
/// Print("Aborting in 3 second...")
///
/// setTimeout(function () {
/// Print("Three")
///
/// setTimeout(function () {
/// Print("Two")
///
/// setTimeout(function () {
/// Print("One")
///
/// setTimeout(function () {
/// App.Command("abort")
/// }, 1000)
/// }, 1000)
/// }, 1000)
/// }, 1000)
/// let counter = 0;
/// setInterval(function () {
/// counter++;
/// Print(counter);
/// }, 1000)
/// ]]>
/// </code>
Expand Down
12 changes: 11 additions & 1 deletion Core/Key2Joy.Core/Mapping/Actions/Scripting/ExposedMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ public object TransformAndRedirect(params object[] parameters)
parameters = new object[] { parameters };
}

// If there's more arguments than parameters, and the last parameter is params,
// we'll wrap the rest of the arguments in an object[] and pass that through.
if (this.IsLastParameterParams)
{
var surplusParameters = parameters.Skip(this.ParameterTypes.Count - 1).ToArray();
var parametersToPass = parameters.Take(this.ParameterTypes.Count - 1).ToList();
parametersToPass.Add(surplusParameters);
parameters = parametersToPass.ToArray();
}

var transformedParameters = parameters
.Select((parameter, parameterIndex) =>
{
Expand All @@ -101,7 +111,7 @@ public object TransformAndRedirect(params object[] parameters)
return TypeConverter.ConvertToType(parameter, parameterType);
})
.ToList(); // ToList allows for easier manipulation than an array.
.ToList();

// Ensure the transformedParameters list has the same number of items as this.ParameterTypes
while (transformedParameters.Count < this.ParameterTypes.Count)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Jint;
Expand All @@ -11,6 +12,7 @@
using Key2Joy.Contracts.Mapping;
using Key2Joy.Contracts.Mapping.Actions;
using Key2Joy.Contracts.Mapping.Triggers;
using Key2Joy.Contracts.Plugins;
using Key2Joy.Util;

namespace Key2Joy.Mapping.Actions.Scripting;
Expand Down Expand Up @@ -78,11 +80,22 @@ public override void RegisterScriptingMethod(ExposedMethod exposedMethod, Abstra
var parents = functionName.Split('.');
var methodInfo = exposedMethod.GetExecutorMethodInfo();
var @delegate = new DelegateWrapper(this.Environment, methodInfo.CreateDelegate(exposedMethod));
var currentObject = this.Environment.Realm.GlobalObject;

// TODO: This may work for the setTimeout and setInterval methods, but will it work for other
// types of functions? I think this Func`3<JsValue, JsValue[], JsValue> may be the result of the
// GetExecutorMethodInfo above. Since that always points to the TransformAndRedirect method
// it may just work for any js function.
exposedMethod.RegisterParameterTransformer<Func<JsValue, JsValue[], JsValue>>(
(jsFunc, expectedType) => new CallbackActionWrapper(
(object[] args) => jsFunc.Invoke(
JsValue.Undefined,
args.Select(a => JsValue.FromObject(this.Environment, a)).ToArray()
))
);

if (parents.Length > 1)
{
var currentObject = this.Environment.Realm.GlobalObject;

for (var i = 0; i < parents.Length; i++)
{
if (i != parents.Length - 1)
Expand All @@ -109,15 +122,11 @@ public override void RegisterScriptingMethod(ExposedMethod exposedMethod, Abstra
return;
}

this.Environment.SetValue(
functionName,
methodInfo);
currentObject.FastSetProperty(functionName, new PropertyDescriptor(@delegate, false, true, true));
}

public override Engine MakeEnvironment() => new Engine(options =>
{
options.Interop.AllowSystemReflection = true;
});
public override Engine MakeEnvironment()
=> new(options => options.Interop.AllowSystemReflection = true);

public override void RegisterEnvironmentObjects()
{
Expand Down
24 changes: 5 additions & 19 deletions Docs/Api/Logic/SetInterval.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,14 @@ Repeatedly calls a function or executes a code snippet, with a fixed time delay
An interval id that can be removed with clearInterval.

## Examples
> Shows how to count up to 10 every second and then stop by using ClearInterval();
> Shows how to count every second
>
> #### _js_:
> ```js
> setTimeout(function () {
> Print("Aborting in 3 second...")
>
> setTimeout(function () {
> Print("Three")
>
> setTimeout(function () {
> Print("Two")
>
> setTimeout(function () {
> Print("One")
>
> setTimeout(function () {
> App.Command("abort")
> }, 1000)
> }, 1000)
> }, 1000)
> }, 1000)
> let counter = 0;
> setInterval(function () {
> counter++;
> Print(counter);
> }, 1000)
> ```
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Jint;
using Key2Joy.Contracts.Plugins;
using Key2Joy.Mapping.Actions;
using Key2Joy.Mapping;
using Key2Joy.Mapping.Actions.Scripting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Key2Joy.Contracts.Mapping.Actions;

namespace Key2Joy.Tests.Core.Mapping.Actions.Scripting;

public class JavascriptMockType : AbstractAction
{
public JavascriptMockType() : base(string.Empty)
{ }

public void MethodWithFunctionParameter(CallbackAction callback, params object[] args)
=> callback(args);
}

internal class TestJavascriptScriptAction : JavascriptAction
{
public TestJavascriptScriptAction(string name) : base(name)
{
}

public Engine GetEnvironment() => this.Environment;
}

[TestClass]
public class JavascriptScriptActionTests
{
[TestMethod]
public void Test_TypeExposedMethod_CanReceiveJavascriptFunction()
{
ActionsRepository.Buffer();
ExposedEnumerationRepository.Buffer();

var javascriptScriptAction = new TestJavascriptScriptAction("TestAction");
var javascript = javascriptScriptAction.SetupEnvironment();

var exposedMethod = new TypeExposedMethod("functionName", nameof(JavascriptMockType.MethodWithFunctionParameter), typeof(JavascriptMockType));

var instance = new JavascriptMockType();
exposedMethod.Prepare(instance);
javascriptScriptAction.RegisterScriptingMethod(
exposedMethod,
instance);

javascript.Execute("var funcResult; functionName(function(){ funcResult = 1337 });");

var actual = javascript.GetValue("funcResult");
Assert.AreEqual((double)1337, actual);
}

[TestMethod]
public void Test_TypeExposedMethod_CanReceiveJavascriptFunctionWithParams()
{
ActionsRepository.Buffer();
ExposedEnumerationRepository.Buffer();

var javascriptScriptAction = new TestJavascriptScriptAction("TestAction");
var javascript = javascriptScriptAction.SetupEnvironment();

var exposedMethod = new TypeExposedMethod("functionName", nameof(JavascriptMockType.MethodWithFunctionParameter), typeof(JavascriptMockType));

var instance = new JavascriptMockType();
exposedMethod.Prepare(instance);
javascriptScriptAction.RegisterScriptingMethod(
exposedMethod,
instance);

javascript.Execute("var funcResult; functionName(function(a,b,c,d){ funcResult = `${a}${b}${c}${d}` }, 1, 3, 3, 7)");

var actual = javascript.GetValue("funcResult");
Assert.AreEqual("1337", actual);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Key2Joy.Contracts.Mapping.Actions;
using Key2Joy.Contracts.Plugins;
using Key2Joy.Mapping;
using Key2Joy.Mapping.Actions;
Expand All @@ -7,10 +8,13 @@

namespace Key2Joy.Tests.Core.Mapping.Actions.Scripting;

public class LuaMockType
public class LuaMockType : AbstractAction
{
public void MethodWithFunctionParameter(CallbackAction callback)
=> callback();
public LuaMockType() : base(string.Empty)
{ }

public void MethodWithFunctionParameter(CallbackAction callback, params object[] args)
=> callback(args);
}

internal class TestLuaScriptAction : LuaScriptAction
Expand Down Expand Up @@ -86,13 +90,20 @@ public void TestLuaScriptActionInequalityBasedOnNameAndScript()
[TestMethod]
public void Test_TypeExposedMethod_CanReceiveLuaFunction()
{
ActionsRepository.Buffer();
ExposedEnumerationRepository.Buffer();

var luaScriptAction = new TestLuaScriptAction("TestAction");
var lua = luaScriptAction.SetupEnvironment();

var exposedMethod = new TypeExposedMethod("functionName", nameof(LuaMockType.MethodWithFunctionParameter), typeof(LuaMockType));

var instance = new LuaMockType();
exposedMethod.Prepare(instance);
exposedMethod.RegisterParameterTransformer<LuaFunction>((luaFunction, expectedType) => new CallbackActionWrapper((object)luaFunction.Call));
luaScriptAction.RegisterScriptingMethod(
exposedMethod,
instance);

var lua = new Lua();
lua.DoString("function funcToPass() funcResult = 1337; end");
var function = lua.GetFunction("funcToPass");

Expand All @@ -102,6 +113,32 @@ public void Test_TypeExposedMethod_CanReceiveLuaFunction()
Assert.AreEqual((double)1337, actual);
}

[TestMethod]
public void Test_TypeExposedMethod_CanReceiveLuaFunctionWithParams()
{
ActionsRepository.Buffer();
ExposedEnumerationRepository.Buffer();

var luaScriptAction = new TestLuaScriptAction("TestAction");
var lua = luaScriptAction.SetupEnvironment();

var exposedMethod = new TypeExposedMethod("functionName", nameof(LuaMockType.MethodWithFunctionParameter), typeof(LuaMockType));

var instance = new LuaMockType();
exposedMethod.Prepare(instance);
luaScriptAction.RegisterScriptingMethod(
exposedMethod,
instance);

lua.DoString("function funcToPass(a, b, c, d) funcResult = tostring(a)..tostring(b)..tostring(c)..tostring(d); end");
var function = lua.GetFunction("funcToPass");

exposedMethod.TransformAndRedirect(new object[] { function, 1, 3, 3, 7 });

var actual = lua["funcResult"];
Assert.AreEqual("1337", actual);
}

// TODO: This currently fails. We need to rethink how we do equality checks (and why)
// the way we currently do... It's tedious to override Equals and GetHashCode for every
// action and I kinda forgot why we have to.
Expand Down

0 comments on commit 606db1b

Please sign in to comment.