import ExpoModulesTestCore @testable import ExpoModulesCore class PropertyDefinitionSpec: ExpoSpec { override class func spec() { describe("property") { let appContext = AppContext.create() let runtime = try! appContext.runtime it("gets the value") { let property = Property("test") { return "expo" } expect(try property.getValue(appContext: appContext)) == "expo" } it("sets the value") { var value = Int.random(in: 0..<100) let property = Property("test") .get { value } .set { (newValue: Int) in value = newValue } let newValue = Int.random(in: 0..<100) property.setValue(newValue, appContext: appContext) expect(try property.getValue(appContext: appContext)) == value expect(value) == newValue } } describe("module property") { let appContext = AppContext.create() let runtime = try! appContext.runtime beforeSuite { appContext.moduleRegistry.register(moduleType: PropertyTestModule.self) } it("gets read-only property") { let value = try runtime.eval("expo.modules.PropertyTest.readOnly") expect(value.getString()) == "foo" } it("gets writable property") { let value = try runtime.eval("expo.modules.PropertyTest.writable") expect(value.getInt()) == 444 } it("sets writable property") { try runtime.eval("expo.modules.PropertyTest.writable = 777") let value = try runtime.eval("expo.modules.PropertyTest.writable") expect(value.getInt()) == 777 } it("is enumerable") { let keys = try runtime.eval("Object.keys(expo.modules.PropertyTest)").getArray().map { $0.getString() } ?? [] expect(keys).to(contain("readOnly", "writable", "undefined")) } // TODO: Using JavaScriptObject as the owner is no longer possible, but we may want to bring this feature back // it("is called with the caller") { // let value = try runtime?.eval("expo.modules.PropertyTest.withCaller") // expect(value?.getString()) == "foo" // } it("returns undefined when getter is not specified") { let value = try runtime.eval("expo.modules.PropertyTest.undefined") expect(value.isUndefined()) == true } } describe("class property") { let appContext = AppContext.create() let runtime = try! appContext.runtime beforeSuite { appContext.moduleRegistry.register(moduleType: PropertyTestModule.self) } it("gets the value") { let value = try runtime.eval("new expo.modules.PropertyTest.TestClass().someValue") expect(value.kind) == .number expect(value.getInt()) == TestClass.constantValue } it("sets the value") { let newValue = Int.random(in: 1..<100) let value = try runtime.eval([ "object = new expo.modules.PropertyTest.TestClass()", "object.someValue = \(newValue)", "object.someValue" ]) expect(value.kind) == .number expect(value.getInt()) == newValue } // Tests for accessing shared object properties through KeyPath and ReferenceWritableKeyPath describe("key path") { it("gets immutable property") { let value = try runtime.eval([ "object = new expo.modules.PropertyTest.TestClass()", "object.immutableKeyPathProperty" ]) expect(value.kind) == .number expect(value.getInt()) == TestClass.constantValue } it("cannot set immutable property") { let newValue = Int.random(in: 100..<200) let value = try runtime.eval([ "object = new expo.modules.PropertyTest.TestClass()", "object.immutableKeyPathProperty = \(newValue)", "object.immutableKeyPathProperty" ]) // Returned value didn't change, it doesn't equal to `newValue` expect(value.kind) == .number expect(value.getInt()) == TestClass.constantValue } it("sets mutable property") { let newValue = Int.random(in: 100..<200) let value = try runtime.eval([ "object = new expo.modules.PropertyTest.TestClass()", "object.mutableKeyPathProperty = \(newValue)", "object.mutableKeyPathProperty" ]) expect(value.kind) == .number expect(value.getInt()) == newValue } } } } } class PropertyTestModule: Module { func definition() -> ModuleDefinition { Name("PropertyTest") Property("readOnly") { return "foo" } var writablePropertyValue = 444 Property("writable") .get { return writablePropertyValue } .set { value in writablePropertyValue = value } // TODO: Using JavaScriptObject as the owner is no longer possible, but we may want to bring this feature back // Property("withCaller") { (caller: JavaScriptObject) -> String in // // Here, the caller is a JS object of the module. // // Return another property of itself. // return caller.getProperty("readOnly").getString() // } Property("undefined") Class(TestClass.self) { Constructor { return TestClass() } Property("someValue") { object in return object.someValue } .set { object, newValue in object.someValue = newValue } // KeyPath Property("immutableKeyPathProperty", \.immutableKeyPathProperty) // ReferenceWritableKeyPath Property("mutableKeyPathProperty", \.mutableKeyPathProperty) } } } fileprivate final class TestClass: SharedObject { static let constantValue = Int.random(in: 1..<100) var someValue = TestClass.constantValue // For "key path" tests let immutableKeyPathProperty = TestClass.constantValue var mutableKeyPathProperty = TestClass.constantValue }