diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 7c19ec8..28f25df 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -12,7 +12,7 @@ type DynamicObj() = #if !FABLE_COMPILER inherit DynamicObject() - #endif + let mutable properties = new Dictionary() @@ -24,14 +24,29 @@ type DynamicObj() = and internal set(value) = properties <- value /// - /// Creates a new DynamicObj from a Dictionary containing dynamic properties. + /// Creates a new DynamicObj from a Dictionary containing dynamic properties. This method is not Fable-compatible. /// /// The dictionary with the dynamic properties - static member ofDict (dynamicProperties: Dictionary) = + static member ofDictInPlace (dynamicProperties: Dictionary) = let obj = DynamicObj() obj.Properties <- dynamicProperties obj + #endif + + /// + /// Creates a new DynamicObj from a Dictionary by deep copying the fields of the dictionary. + /// + /// The dictionary with the dynamic properties + static member ofDict (dynamicProperties: Dictionary) = + let obj = DynamicObj() + dynamicProperties + |> Seq.iter (fun kv -> + let copy = CopyUtils.tryDeepCopyObj(kv.Value,true) + obj.SetProperty(kv.Key,copy) + ) + obj + /// /// Returns Some(PropertyHelper) if a static property with the given name exists, otherwise None. /// @@ -1022,4 +1037,13 @@ and CopyUtils = box newDyn | _ -> o - tryDeepCopyObj o \ No newline at end of file + tryDeepCopyObj o + + +#if !FABLE_COMPILER +[] +module Helper = + + let setProperties (dynObj: DynamicObj) (properties: Dictionary) = + dynObj.Properties <- properties +#endif \ No newline at end of file diff --git a/src/DynamicObj/FableJS.fs b/src/DynamicObj/FableJS.fs index 16c384e..90166cb 100644 --- a/src/DynamicObj/FableJS.fs +++ b/src/DynamicObj/FableJS.fs @@ -158,6 +158,7 @@ module FableJS = getPropertyHelpers o |> Array.map (fun h -> h.Name) + module Interfaces = [] diff --git a/tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs b/tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs index 1d7acb4..dcb5e8b 100644 --- a/tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs +++ b/tests/DynamicObject.Tests/DynamicObj/TryGetPropertyHelper.fs @@ -19,6 +19,7 @@ let tests_TryGetPropertyHelper = testList "TryGetPropertyHelper" [ Expect.isTrue b.IsMutable "Properties should be mutable" Expect.isFalse b.IsImmutable "Properties should not be immutable" + #if !FABLE_COMPILER // Properties field is dotnet only as js and py use native properties testCase "Existing static property" <| fun _ -> let a = DynamicObj() let b = Expect.wantSome (a.TryGetPropertyHelper("Properties")) "Value should exist" @@ -26,4 +27,5 @@ let tests_TryGetPropertyHelper = testList "TryGetPropertyHelper" [ Expect.isFalse b.IsDynamic "Properties should not be dynamic" Expect.isTrue b.IsMutable "Properties should be mutable" Expect.isFalse b.IsImmutable "Properties should not be immutable" + #endif ] \ No newline at end of file diff --git a/tests/DynamicObject.Tests/DynamicObj/TryGetStaticPropertyHelper.fs b/tests/DynamicObject.Tests/DynamicObj/TryGetStaticPropertyHelper.fs index 666bb20..0f14ffb 100644 --- a/tests/DynamicObject.Tests/DynamicObj/TryGetStaticPropertyHelper.fs +++ b/tests/DynamicObject.Tests/DynamicObj/TryGetStaticPropertyHelper.fs @@ -10,6 +10,7 @@ let tests_TryGetStaticPropertyHelper = testList "TryGetStaticPropertyHelper" [ let b = a.TryGetStaticPropertyHelper("a") Expect.isNone b "Value should not exist" + #if !FABLE_COMPILER // Properties field is dotnet only as js and py use native properties testCase "Properties dictionary is static property" <| fun _ -> let a = DynamicObj() let b = Expect.wantSome (a.TryGetStaticPropertyHelper("Properties")) "Value should exist" @@ -17,6 +18,7 @@ let tests_TryGetStaticPropertyHelper = testList "TryGetStaticPropertyHelper" [ Expect.isFalse b.IsDynamic "Properties should not be dynamic" Expect.isTrue b.IsMutable "Properties should be mutable" Expect.isFalse b.IsImmutable "Properties should not be immutable" + #endif testCase "dynamic property not retrieved as static" <| fun _ -> let a = DynamicObj() diff --git a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj index 30c418e..fec2ad1 100644 --- a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj +++ b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj @@ -34,6 +34,7 @@ + diff --git a/tests/DynamicObject.Tests/InheritanceView.fs b/tests/DynamicObject.Tests/InheritanceView.fs new file mode 100644 index 0000000..991c1bb --- /dev/null +++ b/tests/DynamicObject.Tests/InheritanceView.fs @@ -0,0 +1,176 @@ +module InheritanceView.Tests + +open System +open Fable.Pyxpecto +open DynamicObj +open Fable.Core + +let id = "id" +let firstName = "firstName" +let lastName = "lastName" + +// We introduce and test here the concept of an inheritanceView: +// A derived type that is inherits a base type. +// If the constructor of the derived type is called with a base type as input, no new object is created, but the base type is used as the base object. +// The derived type can therefore be seen as a view on the base type, with additional methods to access the dynamic properties of the base type. + + +[] +type BaseType(?baseOn : BaseType) + #if !FABLE_COMPILER + as self + #endif + = + + inherit DynamicObj() + + let _ = + + match baseOn with + | Some dynOb -> + #if !FABLE_COMPILER + DynamicObj.Helper.setProperties self dynOb.Properties + #endif + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + do Fable.Core.JsInterop.emitJsStatement "" """ + const protoType = Object.getPrototypeOf(this); + Object.setPrototypeOf(baseOn, protoType); + return baseOn; + """ + #endif + #if FABLE_COMPILER_PYTHON + () + #endif + | None -> () + + #if FABLE_COMPILER_PYTHON + do Fable.Core.PyInterop.emitPyStatement "" """ + def __new__(cls, base_on: "BaseType | None" = None): + if base_on is not None and isinstance(base_on, cls): + return base_on + + if base_on is not None: + base_on.__class__ = cls + return base_on + + return super().__new__(cls) + """ + #endif + + member this.GetID() = this.GetPropertyValue(id) + + member this.SetID(value) = this.SetProperty(id, value) + + member this.GetFirstName() = this.GetPropertyValue(firstName) + + member this.SetFirstName(value) = this.SetProperty(firstName, value) + +[] +type DerivedType(?baseOn : BaseType) = + + inherit BaseType(?baseOn = baseOn) + + member this.GetLastName() = this.GetPropertyValue(lastName) + + member this.SetLastName(value) = this.SetProperty(lastName, value) + + +let tests_baseType = testList "BaseType" [ + + testCase "AnotherPerson" <| fun _ -> + let p1 = BaseType() + let name = "John" + p1.SetFirstName(name) + let id = "123" + p1.SetID(id) + Expect.equal (p1.GetFirstName()) name "P1: First name should be set" + Expect.equal (p1.GetID()) id "P1: ID should be set" + let p2 = BaseType(baseOn = p1) + Expect.equal (p2.GetFirstName()) name "P2: First name should be set" + Expect.equal (p2.GetID()) id "P2: ID should be set" + + testCase "InheritedMutability" <| fun _ -> + let p1 = BaseType() + let name = "John" + p1.SetFirstName(name) + + let p2 = BaseType(baseOn = p1) + let newName = "Jane" + p2.SetFirstName(newName) + Expect.equal (p2.GetFirstName()) newName "P2: First name should be set" + Expect.equal (p1.GetFirstName()) newName "P1: First name should be set" +] + +let tests_derivedType = testList "DerivedType" [ + + testCase "OnBase_AnotherPerson" <| fun _ -> + let p1 = BaseType() + let name = "John" + p1.SetFirstName(name) + let id = "123" + p1.SetID(id) + let lN = "Doe" + p1.SetProperty(lastName,lN) + Expect.equal (p1.GetFirstName()) name "P1: First name should be set" + Expect.equal (p1.GetID()) id "P1: ID should be set" + Expect.equal (p1.GetPropertyValue(lastName)) lN "P1: Last name should be set" + let p2 = DerivedType(baseOn = p1) + Expect.equal (p2.GetFirstName()) name "P2: First name should be set" + Expect.equal (p2.GetID()) id "P2: ID should be set" + Expect.equal (p2.GetLastName()) lN "P2: Last name should be set" + + testCase "OnBase_InheritedMutability" <| fun _ -> + let p1 = BaseType() + let name = "John" + p1.SetFirstName(name) + let lN = "Doe" + p1.SetProperty(lastName,lN) + + let p2 = DerivedType(baseOn = p1) + let newName = "Jane" + p2.SetFirstName(newName) + let newLastName = "Smith" + p2.SetLastName(newLastName) + Expect.equal (p2.GetFirstName()) newName "P2: First name should be set" + Expect.equal (p2.GetLastName()) newLastName "P2: Last name should be set" + Expect.equal (p1.GetFirstName()) newName "P1: First name should be set" + Expect.equal (p1.GetPropertyValue(lastName)) newLastName "P1: Last name should be set" + + testCase "OnDerived_AnotherPerson" <| fun _ -> + let p1 = DerivedType() + let name = "John" + p1.SetFirstName(name) + let id = "123" + p1.SetID(id) + let lastName = "Doe" + p1.SetLastName(lastName) + Expect.equal (p1.GetFirstName()) name "P1: First name should be set" + Expect.equal (p1.GetID()) id "P1: ID should be set" + Expect.equal (p1.GetLastName()) lastName "P1: Last name should be set" + let p2 = DerivedType(baseOn = p1) + Expect.equal (p2.GetFirstName()) name "P2: First name should be set" + Expect.equal (p2.GetID()) id "P2: ID should be set" + Expect.equal (p2.GetLastName()) lastName "P2: Last name should be set" + + testCase "OnDerived_InheritedMutability" <| fun _ -> + let p1 = DerivedType() + let firstName = "John" + p1.SetFirstName(firstName) + let lastName = "Doe" + p1.SetLastName(lastName) + + let p2 = DerivedType(baseOn = p1) + let newName = "Jane" + p2.SetFirstName(newName) + let newLastName = "Smith" + p2.SetLastName(newLastName) + Expect.equal (p2.GetFirstName()) newName "P2: First name should be set" + Expect.equal (p2.GetLastName()) newLastName "P2: Last name should be set" + Expect.equal (p1.GetFirstName()) newName "P1: First name should be set" + Expect.equal (p1.GetLastName()) newLastName "P1: Last name should be set" +] + +let main = testList "InheritanceView" [ + tests_baseType + tests_derivedType +] \ No newline at end of file diff --git a/tests/DynamicObject.Tests/Main.fs b/tests/DynamicObject.Tests/Main.fs index beed87f..9894ee0 100644 --- a/tests/DynamicObject.Tests/Main.fs +++ b/tests/DynamicObject.Tests/Main.fs @@ -9,6 +9,7 @@ let all = testSequenced <| testList "DynamicObj" [ DynamicObj.Tests.main DynObj.Tests.main Inheritance.Tests.main + InheritanceView.Tests.main Interface.Tests.main Serialization.Tests.main ]