using Jint.Native; using Jint.Native.Global; using Jint.Runtime.Descriptors; using Jint.Runtime.Descriptors.Specialized; using Jint.Runtime.Interop; using Jint.Tests.TestClasses; namespace Jint.Tests.Runtime; public class PropertyDescriptorTests { public class TestClass { public static readonly TestClass Instance = new TestClass(); public string Method() => "Method"; public class NestedType { } public readonly int fieldReadOnly = 8; public int field = 42; public string PropertyReadOnly => "PropertyReadOnly"; public string PropertyWriteOnly { set { } } public string PropertyReadWrite { get; set; } = "PropertyReadWrite"; public IndexedPropertyReadOnly IndexerReadOnly { get; } = new((idx) => 42); public IndexedPropertyWriteOnly IndexerWriteOnly { get; } = new((idx, v) => { }); public IndexedProperty IndexerReadWrite { get; } = new((idx) => 42, (idx, v) => { }); } private readonly Engine _engine; private readonly bool checkType = true; public PropertyDescriptorTests() { _engine = new Engine(cfg => cfg.AllowClr( typeof(TestClass).Assembly, typeof(Console).Assembly, typeof(File).Assembly)) .SetValue("log", new Action(Console.WriteLine)) .SetValue("assert", new Action(Assert.True)) .SetValue("equal", new Action(Assert.Equal)) .SetValue("testClass", TestClass.Instance) ; } [Fact] public void PropertyDescriptorReadOnly() { var pd = _engine.Evaluate(""" Object.defineProperty({}, 'value', { value: 42, writable: false }) """).AsObject().GetOwnProperty("value"); Assert.Equal(false, pd.IsAccessorDescriptor()); Assert.Equal(true, pd.IsDataDescriptor()); Assert.Equal(false, pd.Writable); Assert.Null(pd.Get); Assert.Null(pd.Set); } [Fact] public void PropertyDescriptorReadWrite() { var pd = _engine.Evaluate(""" Object.defineProperty({}, 'value', { value: 42, writable: true }) """).AsObject().GetOwnProperty("value"); Assert.Equal(false, pd.IsAccessorDescriptor()); Assert.Equal(true, pd.IsDataDescriptor()); Assert.Equal(true, pd.Writable); Assert.Null(pd.Get); Assert.Null(pd.Set); } [Fact] public void UndefinedPropertyDescriptor() { var pd = PropertyDescriptor.Undefined; // PropertyDescriptor.UndefinedPropertyDescriptor is private //if (checkType) Assert.IsType(pd); Assert.Equal(false, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void AllForbiddenDescriptor() { var pd = _engine.Evaluate("Object.getPrototypeOf('s')").AsObject().GetOwnProperty("length"); if (checkType) Assert.IsType(pd); Assert.Equal(false, pd.IsAccessorDescriptor()); Assert.Equal(true, pd.IsDataDescriptor()); } [Fact] public void LazyPropertyDescriptor() { var pd = _engine.Evaluate("globalThis").AsObject().GetOwnProperty("decodeURI"); if (checkType) { Assert.IsType>(pd); } Assert.Equal(false, pd.IsAccessorDescriptor()); Assert.Equal(true, pd.IsDataDescriptor()); } [Fact] public void ThrowerPropertyDescriptor() { var pd = _engine.Evaluate("Object.getPrototypeOf(function() {})").AsObject().GetOwnProperty("arguments"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void GetSetPropertyDescriptorGetOnly() { var pd = _engine.Evaluate(""" Object.defineProperty({}, 'value', { get() {} }) """).AsObject().GetOwnProperty("value"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); Assert.NotNull(pd.Get); Assert.Null(pd.Set); } [Fact] public void GetSetPropertyDescriptorSetOnly() { var pd = _engine.Evaluate(""" Object.defineProperty({}, 'value', { set() {} }) """).AsObject().GetOwnProperty("value"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); Assert.Null(pd.Get); Assert.NotNull(pd.Set); } [Fact] public void GetSetPropertyDescriptorGetSet() { var pd = _engine.Evaluate(""" Object.defineProperty({}, 'value', { get() {}, set() {} }) """).AsObject().GetOwnProperty("value"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); Assert.NotNull(pd.Get); Assert.NotNull(pd.Set); } [Fact] public void ClrAccessDescriptor() { JsValue ExtractClrAccessDescriptor(JsValue jsArugments) { var pd = ((JsArguments) jsArugments).ParameterMap.GetOwnProperty("0"); return ObjectWrapper.Create(_engine, pd); } _engine.SetValue("ExtractClrAccessDescriptor", ExtractClrAccessDescriptor); var pdobj = _engine.Evaluate(""" (function(a) { return ExtractClrAccessDescriptor(arguments); })(42) """); var pd = (PropertyDescriptor) ((ObjectWrapper) pdobj).Target; if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void PropertyDescriptorMethod() { var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'Method')"); CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false); var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("Method"); // use PropertyDescriptor to wrap method directly //if (checkType) Assert.IsType(pd); Assert.Equal(false, pd.IsAccessorDescriptor()); Assert.Equal(true, pd.IsDataDescriptor()); } [Fact] public void PropertyDescriptorNestedType() { var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'NestedType')"); CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false); var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("NestedType"); // use PropertyDescriptor to wrap nested type directly //if (checkType) Assert.IsType(pd); Assert.Equal(false, pd.IsAccessorDescriptor()); Assert.Equal(true, pd.IsDataDescriptor()); } [Fact] public void ReflectionDescriptorFieldReadOnly() { var pdField = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'fieldReadOnly')"); CheckPropertyDescriptor(pdField, false, true, false, false, true, false); var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("fieldReadOnly"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void ReflectionDescriptorField() { var pdField = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'field')"); CheckPropertyDescriptor(pdField, false, true, true, false, true, true); var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("field"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void ReflectionDescriptorPropertyReadOnly() { var pdPropertyReadOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyReadOnly')"); CheckPropertyDescriptor(pdPropertyReadOnly, false, true, false, false, true, false); var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyReadOnly"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void ReflectionDescriptorPropertyWriteOnly() { var pdPropertyWriteOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyWriteOnly')"); CheckPropertyDescriptor(pdPropertyWriteOnly, false, true, true, false, false, true); var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyWriteOnly"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void ReflectionDescriptorPropertyReadWrite() { var pdPropertyReadWrite = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyReadWrite')"); CheckPropertyDescriptor(pdPropertyReadWrite, false, true, true, false, true, true); var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyReadWrite"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void ReflectionDescriptorIndexerReadOnly() { var pdIndexerReadOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerReadOnly, '1')"); CheckPropertyDescriptor(pdIndexerReadOnly, false, true, false, false, true, false); var pd1 = _engine.Evaluate("testClass.IndexerReadOnly"); var pd = pd1.AsObject().GetOwnProperty("1"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void ReflectionDescriptorIndexerWriteOnly() { var pdIndexerWriteOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerWriteOnly, '1')"); CheckPropertyDescriptor(pdIndexerWriteOnly, false, true, true, false, false, true); var pd = _engine.Evaluate("testClass.IndexerWriteOnly").AsObject().GetOwnProperty("1"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } [Fact] public void ReflectionDescriptorIndexerReadWrite() { var pdIndexerReadWrite = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerReadWrite, 1)"); CheckPropertyDescriptor(pdIndexerReadWrite, false, true, true, false, true, true); var pd = _engine.Evaluate("testClass.IndexerReadWrite").AsObject().GetOwnProperty("1"); if (checkType) Assert.IsType(pd); Assert.Equal(true, pd.IsAccessorDescriptor()); Assert.Equal(false, pd.IsDataDescriptor()); } private void CheckPropertyDescriptor( JsValue jsPropertyDescriptor, bool configurable, bool enumerable, bool writable, bool hasValue, bool hasGet, bool hasSet ) { var pd = jsPropertyDescriptor.AsObject(); Assert.Equal(configurable, pd["configurable"].AsBoolean()); Assert.Equal(enumerable, pd["enumerable"].AsBoolean()); if (writable) { var writableActual = pd["writable"]; if (!writableActual.IsUndefined()) { Assert.True(writableActual.AsBoolean()); } } Assert.Equal(hasValue, !pd["value"].IsUndefined()); Assert.Equal(hasGet, !pd["get"].IsUndefined()); Assert.Equal(hasSet, !pd["set"].IsUndefined()); } [Fact] public void DefinePropertyFromAccesorToData() { var pd = _engine.Evaluate(""" let o = {}; Object.defineProperty(o, 'foo', { get() { return 1; }, configurable: true }); Object.defineProperty(o, 'foo', { value: 101 }); return Object.getOwnPropertyDescriptor(o, 'foo'); """); Assert.Equal(101, pd.AsObject().Get("value").AsInteger()); CheckPropertyDescriptor(pd, true, false, false, true, false, false); } }