1 import ExpoModulesTestCore
2 
3 @testable import ExpoModulesCore
4 
5 class PropertyComponentSpec: ExpoSpec {
specnull6   override func spec() {
7     describe("property") {
8       let appContext = AppContext.create()
9       let runtime = try! appContext.runtime
10 
11       it("gets the value") {
12         let property = Property("test") { return "expo" }
13         expect(try property.getValue(appContext: appContext)) == "expo"
14       }
15 
16       it("sets the value") {
17         var value = Int.random(in: 0..<100)
18         let property = Property("test")
19           .get { value }
20           .set { (newValue: Int) in
21             value = newValue
22           }
23 
24         let newValue = Int.random(in: 0..<100)
25         property.setValue(newValue, appContext: appContext)
26 
27         expect(try property.getValue(appContext: appContext)) == value
28         expect(value) == newValue
29       }
30     }
31 
32     describe("module property") {
33       let appContext = AppContext.create()
34       let runtime = try! appContext.runtime
35 
36       beforeSuite {
37         appContext.moduleRegistry.register(moduleType: PropertyTestModule.self)
38       }
39 
40       it("gets read-only property") {
41         let value = try runtime.eval("expo.modules.PropertyTest.readOnly")
42         expect(value.getString()) == "foo"
43       }
44 
45       it("gets writable property") {
46         let value = try runtime.eval("expo.modules.PropertyTest.writable")
47         expect(value.getInt()) == 444
48       }
49 
50       it("sets writable property") {
51         try runtime.eval("expo.modules.PropertyTest.writable = 777")
52         let value = try runtime.eval("expo.modules.PropertyTest.writable")
53         expect(value.getInt()) == 777
54       }
55 
56       it("is enumerable") {
57         let keys = try runtime.eval("Object.keys(expo.modules.PropertyTest)").getArray().map { $0.getString() } ?? []
58         expect(keys).to(contain("readOnly", "writable", "undefined"))
59       }
60 
61 // TODO: Using JavaScriptObject as the owner is no longer possible, but we may want to bring this feature back
62 //      it("is called with the caller") {
63 //        let value = try runtime?.eval("expo.modules.PropertyTest.withCaller")
64 //        expect(value?.getString()) == "foo"
65 //      }
66 
67       it("returns undefined when getter is not specified") {
68         let value = try runtime.eval("expo.modules.PropertyTest.undefined")
69         expect(value.isUndefined()) == true
70       }
71     }
72 
73     describe("class property") {
74       let appContext = AppContext.create()
75       let runtime = try! appContext.runtime
76 
77       beforeSuite {
78         appContext.moduleRegistry.register(moduleType: PropertyTestModule.self)
79       }
80 
81       it("gets the value") {
82         let value = try runtime.eval("new expo.modules.PropertyTest.TestClass().someValue")
83 
84         expect(value.kind) == .number
85         expect(value.getInt()) == TestClass.constantValue
86       }
87 
88       it("sets the value") {
89         let newValue = Int.random(in: 1..<100)
90         let value = try runtime.eval([
91           "object = new expo.modules.PropertyTest.TestClass()",
92           "object.someValue = \(newValue)",
93           "object.someValue"
94         ])
95 
96         expect(value.kind) == .number
97         expect(value.getInt()) == newValue
98       }
99 
100       // Tests for accessing shared object properties through KeyPath and ReferenceWritableKeyPath
101       describe("key path") {
102         it("gets immutable property") {
103           let value = try runtime.eval([
104             "object = new expo.modules.PropertyTest.TestClass()",
105             "object.immutableKeyPathProperty"
106           ])
107 
108           expect(value.kind) == .number
109           expect(value.getInt()) == TestClass.constantValue
110         }
111 
112         it("cannot set immutable property") {
113           let newValue = Int.random(in: 100..<200)
114           let value = try runtime.eval([
115             "object = new expo.modules.PropertyTest.TestClass()",
116             "object.immutableKeyPathProperty = \(newValue)",
117             "object.immutableKeyPathProperty"
118           ])
119 
120           // Returned value didn't change, it doesn't equal to `newValue`
121           expect(value.kind) == .number
122           expect(value.getInt()) == TestClass.constantValue
123         }
124 
125         it("sets mutable property") {
126           let newValue = Int.random(in: 100..<200)
127           let value = try runtime.eval([
128             "object = new expo.modules.PropertyTest.TestClass()",
129             "object.mutableKeyPathProperty = \(newValue)",
130             "object.mutableKeyPathProperty"
131           ])
132 
133           expect(value.kind) == .number
134           expect(value.getInt()) == newValue
135         }
136       }
137     }
138   }
139 }
140 
141 class PropertyTestModule: Module {
definitionnull142   func definition() -> ModuleDefinition {
143     Name("PropertyTest")
144 
145     Property("readOnly") {
146       return "foo"
147     }
148 
149     var writablePropertyValue = 444
150     Property("writable")
151       .get {
152         return writablePropertyValue
153       }
154       .set { value in
155         writablePropertyValue = value
156       }
157 
158 // TODO: Using JavaScriptObject as the owner is no longer possible, but we may want to bring this feature back
159 //            Property("withCaller") { (caller: JavaScriptObject) -> String in
160 //              // Here, the caller is a JS object of the module.
161 //              // Return another property of itself.
162 //              return caller.getProperty("readOnly").getString()
163 //            }
164 
165     Property("undefined")
166 
167     Class(TestClass.self) {
168       Constructor {
169         return TestClass()
170       }
171 
172       Property("someValue") { object in
173         return object.someValue
174       }
175       .set { object, newValue in
176         object.someValue = newValue
177       }
178 
179       // KeyPath<TestClass, Int>
180       Property("immutableKeyPathProperty", \.immutableKeyPathProperty)
181 
182       // ReferenceWritableKeyPath<TestClass, Int>
183       Property("mutableKeyPathProperty", \.mutableKeyPathProperty)
184     }
185   }
186 }
187 
188 fileprivate final class TestClass: SharedObject {
189   static let constantValue = Int.random(in: 1..<100)
190 
191   var someValue = TestClass.constantValue
192 
193   // For "key path" tests
194   let immutableKeyPathProperty = TestClass.constantValue
195   var mutableKeyPathProperty = TestClass.constantValue
196 }
197