using Jint.Constraints;
using Jint.Native;
using Jint.Native.Function;
using Jint.Runtime;
using Jint.Runtime.Interop;
namespace Jint.Tests.PublicInterface;
///
/// Tests related to functionality that RavenDB needs exposed.
///
public class RavenApiUsageTests
{
[Fact]
public void CanBuildCustomScriptFunctionInstance()
{
var engine = new Engine();
var properties = new Node[]
{
new ObjectProperty(PropertyKind.Init, new Identifier("field"),
new MemberExpression(new Identifier("self"), new Identifier("field"), computed: false, optional: false), false, false, false)
};
var functionExp = new FunctionExpression(
new Identifier("functionId"),
NodeList.From(new Identifier("self")),
new FunctionBody(NodeList.From(new ReturnStatement(new ObjectExpression(NodeList.From(properties)))), strict: false),
generator: false,
async: false);
var functionObject = new ScriptFunction(
engine,
functionExp,
strict: false);
Assert.NotNull(functionObject);
}
[Fact]
public void CanChangeMaxStatementValue()
{
var engine = new Engine(options => options.MaxStatements(123));
var constraint = engine.Constraints.Find();
Assert.NotNull(constraint);
var oldMaxStatements = constraint.MaxStatements;
constraint.MaxStatements = 321;
Assert.Equal(123, oldMaxStatements);
Assert.Equal(321, constraint.MaxStatements);
}
[Fact]
public void CanGetPropertyDescriptor()
{
var engine = new Engine();
var obj = new DirectoryInfo("the-path");
var propertyDescriptor = ObjectWrapper.GetPropertyDescriptor(engine, obj, obj.GetType().GetProperty(nameof(DirectoryInfo.Name)));
Assert.Equal("the-path", propertyDescriptor.Value);
}
[Fact]
public void CanInjectConstructedObjects()
{
var engine = new Engine();
var obj = new JsObject(engine);
obj.FastSetDataProperty("name", "test");
var array1 = new JsArray(engine, [
JsNumber.Create(1),
JsNumber.Create(2),
JsNumber.Create(3)
]);
engine.SetValue("array1", array1);
TestArrayAccess(engine, array1, "array1");
engine.SetValue("obj", obj);
Assert.Equal("test", engine.Evaluate("obj.name"));
engine.SetValue("emptyArray", new JsArray(engine));
Assert.Equal(0, engine.Evaluate("emptyArray.length"));
Assert.Equal(1, engine.Evaluate("emptyArray.push(1); return emptyArray.length"));
engine.SetValue("emptyArray", new JsArray(engine, []));
Assert.Equal(0, engine.Evaluate("emptyArray.length"));
Assert.Equal(1, engine.Evaluate("emptyArray.push(1); return emptyArray.length"));
engine.SetValue("date", new JsDate(engine, new DateTime(2022, 10, 20)));
Assert.Equal(2022, engine.Evaluate("date.getFullYear()"));
}
private static void TestArrayAccess(Engine engine, JsArray array, string name)
{
Assert.Equal(1, engine.Evaluate($"{name}.findIndex(x => x === 2)"));
Assert.Equal(2, array.GetOwnProperty("1").Value);
array.Push(4);
array.Push([5, 6]);
var i = 0;
foreach (var entry in array.GetEntries())
{
Assert.Equal(i.ToString(), entry.Key);
Assert.Equal(i + 1, entry.Value);
i++;
}
Assert.Equal(6, i);
array[0] = "";
array[1] = false;
array[2] = null;
Assert.Equal("", array[0]);
Assert.Equal(false, array[1]);
Assert.Equal(JsValue.Undefined, array[2]);
Assert.Equal(4, array[3]);
Assert.Equal(5, array[4]);
Assert.Equal(6, array[5]);
for (i = 0; i < 100; ++i)
{
array.Push(JsValue.Undefined);
}
Assert.Equal(106L, array.Length);
Assert.True(array.All(x => x is JsNumber or JsUndefined or JsNumber or JsString or JsBoolean));
}
// Checks different ways how string can be checked for equality without the need to materialize lazy value
[Fact]
public void CanInheritCustomString()
{
var engine = new Engine();
var str = new CustomString("the-value");
engine.SetValue("str", str);
var empty = new CustomString("");
engine.SetValue("empty", empty);
engine.SetValue("x", new CustomString("x", allowMaterialize: true));
var obj = new JsObject(engine);
obj.Set("name", new CustomString("the name"));
engine.SetValue("obj", obj);
var array = new JsArray(engine, Enumerable.Range(1, 100).Select(x => new CustomString(x.ToString())).ToArray());
engine.SetValue("array", array);
Assert.True(engine.Evaluate("str ? true : false").AsBoolean());
Assert.False(engine.Evaluate("empty ? true : false").AsBoolean());
Assert.True(engine.Evaluate("array.includes('2')").AsBoolean());
Assert.True(engine.Evaluate("array.filter(x => x === '2').length > 0").AsBoolean());
engine.SetValue("objArray", new JsArray(engine, [obj, obj]));
Assert.True(engine.Evaluate("objArray.filter(x => x.name === 'the name').length === 2").AsBoolean());
Assert.Equal(9, engine.Evaluate("str.length"));
Assert.True(engine.Evaluate("str == 'the-value'").AsBoolean());
Assert.True(engine.Evaluate("str === 'the-value'").AsBoolean());
Assert.True(engine.Evaluate("str.indexOf('value-too-long') === -1").AsBoolean());
Assert.True(engine.Evaluate("str.lastIndexOf('value-too-long') === -1").AsBoolean());
Assert.False(engine.Evaluate("str.startsWith('value-too-long')").AsBoolean());
Assert.False(engine.Evaluate("str.endsWith('value-too-long')").AsBoolean());
Assert.False(engine.Evaluate("str.includes('value-too-long')").AsBoolean());
Assert.True(engine.Evaluate("empty.trim() === ''").AsBoolean());
Assert.True(engine.Evaluate("empty.trimStart() === ''").AsBoolean());
Assert.True(engine.Evaluate("empty.trimEnd() === ''").AsBoolean());
Assert.True(engine.Evaluate("str[1] === 'h'").AsBoolean());
Assert.True(engine.Evaluate("str[x] === undefined").AsBoolean());
}
[Fact]
public void CanDefineCustomNull()
{
var engine = new Engine();
engine.SetValue("value", new CustomNull());
Assert.Equal("foo", engine.Evaluate("value ? value + 'bar' : 'foo'"));
}
[Fact]
public void CanDefineCustomUndefined()
{
var engine = new Engine();
engine.SetValue("value", new CustomUndefined());
Assert.Equal("foo", engine.Evaluate("value ? value + 'bar' : 'foo'"));
}
[Fact]
public void CanResetCallStack()
{
var engine = new Engine();
engine.Advanced.ResetCallStack();
}
[Fact]
public void CanUseCustomReferenceResolver()
{
var engine = new Engine(options =>
{
options.ReferenceResolver = new MyReferenceResolver();
});
engine
.Execute("""
function output(doc) {
var rows123 = [{}];
var test = null;
return {
Rows : [{}].map(row=>({row:row, myRows:test.filter(x=>x)
})).map(__rvn4=>({
Custom:__rvn4.myRows[0].Custom,
Custom2:__rvn4.myRows
}))
};
}
""");
var result = engine.Evaluate("output()");
var rows = result.AsObject()["Rows"];
var custom = rows.AsArray()[0].AsObject()["Custom"];
Assert.Equal(JsValue.Null, custom);
}
}
file sealed class CustomString : JsString
{
private readonly string _value;
private readonly bool _allowMaterialize;
public CustomString(string value, bool allowMaterialize = false) : base(null)
{
_value = value;
_allowMaterialize = allowMaterialize;
}
public override string ToString()
{
if (!_allowMaterialize)
{
// when called we know that we couldn't use fast paths
throw new InvalidOperationException("I don't want to be materialized!");
}
return _value;
}
public override char this[int index] => _value[index];
public override int Length => _value.Length;
public override bool Equals(JsString obj)
{
return obj switch
{
CustomString customString => _value == customString._value,
_ => _value == obj.ToString()
};
}
protected override bool IsLooselyEqual(JsValue value)
{
return value switch
{
CustomString customString => _value == customString._value,
JsString jsString => _value == jsString.ToString(),
_ => base.IsLooselyEqual(value)
};
}
public override int GetHashCode()
{
return _value.GetHashCode();
}
}
file sealed class CustomNull : JsValue
{
public CustomNull() : base(Types.Null)
{
}
public override object ToObject()
{
return null;
}
public override string ToString()
{
return "null";
}
}
file sealed class CustomUndefined : JsValue
{
public CustomUndefined() : base(Types.Null)
{
}
public override object ToObject()
{
return null;
}
public override string ToString()
{
return "null";
}
}
file sealed class MyReferenceResolver : IReferenceResolver
{
public bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value)
{
JsValue referencedName = reference.ReferencedName;
if (referencedName.IsString())
{
value = reference.IsPropertyReference ? JsValue.Undefined : JsValue.Null;
return true;
}
throw new InvalidOperationException();
}
public bool TryPropertyReference(Engine engine, Reference reference, ref JsValue value)
{
return value.IsNull() || value.IsUndefined();
}
public bool TryGetCallable(Engine engine, object callee, out JsValue value)
{
value = new ClrFunction(engine, "function", static (_, _) => JsValue.Undefined);
return true;
}
public bool CheckCoercible(JsValue value)
{
return true;
}
}