Files
Schnappix/node_modules/expo-modules-core/ios/Tests/ClassDefinitionSpec.swift
T

297 lines
9.1 KiB
Swift

import ExpoModulesTestCore
@testable import ExpoModulesCore
class ClassDefinitionSpec: ExpoSpec {
override class func spec() {
describe("basic") {
it("factory returns a definition") {
let klass = Class("") {}
expect(klass).to(beAnInstanceOf(ClassDefinition.self))
}
it("has a name") {
let expoClassName = "ExpoClass"
let klass = Class(expoClassName) {}
expect(klass.name) == expoClassName
}
it("is without native constructor") {
let klass = Class("") {}
expect(klass.constructor).to(beNil())
}
it("has native constructor") {
let klass = Class("") { Constructor {} }
expect(klass.constructor).notTo(beNil())
}
it("ignores constructor as function") {
let klass = Class("") { Constructor {} }
expect(klass.functions["constructor"]).to(beNil())
}
it("builds a class") {
let appContext = AppContext.create()
let klass = Class("") {}
let object = try klass.build(appContext: appContext)
expect(object.hasProperty("prototype")) == true
expect(object.getProperty("prototype").kind) == .object
}
}
describe("module") {
let appContext = AppContext.create()
let runtime = try! appContext.runtime
beforeSuite {
class ClassTestModule: Module {
func definition() -> ModuleDefinition {
Name("ClassTest")
Class("MyClass") {
Constructor {}
Function("myFunction") {
return "foobar"
}
Property("foo") {
return "bar"
}
}
}
}
appContext.moduleRegistry.register(moduleType: ClassTestModule.self)
}
it("is a function") {
let klass = try runtime.eval("expo.modules.ClassTest.MyClass")
expect(klass.isFunction()) == true
}
it("has a name") {
let klass = try runtime.eval("expo.modules.ClassTest.MyClass.name")
expect(klass.getString()) == "MyClass"
}
it("has a prototype") {
let prototype = try runtime.eval("expo.modules.ClassTest.MyClass.prototype")
expect(prototype.isObject()) == true
}
it("has keys in prototype") {
let prototypeKeys = try runtime.eval("Object.keys(expo.modules.ClassTest.MyClass.prototype)")
.getArray()
.map { $0.getString() }
expect(prototypeKeys).to(contain("myFunction"))
expect(prototypeKeys).notTo(contain("__native_constructor__"))
}
it("is an instance of") {
let isInstanceOf = try runtime.eval([
"myObject = new expo.modules.ClassTest.MyClass()",
"myObject instanceof expo.modules.ClassTest.MyClass",
])
expect(isInstanceOf.getBool()) == true
}
it("defines properties on initialization") {
// The properties are not specified in the prototype, but defined during initialization.
let object = try runtime.eval("new expo.modules.ClassTest.MyClass()").asObject()
expect(object.getPropertyNames()).to(contain("foo"))
expect(object.getProperty("foo").getString()) == "bar"
}
}
describe("class with associated type") {
let appContext = AppContext.create()
let runtime = try! appContext.runtime
beforeSuite {
appContext.moduleRegistry.register(moduleType: ModuleWithCounterClass.self)
}
it("is defined") {
let isDefined = try runtime.eval("'Counter' in expo.modules.TestModule")
expect(isDefined.getBool()) == true
}
it("creates shared object") {
let jsObject = try runtime.eval("new expo.modules.TestModule.Counter(0)").getObject()
let nativeObject = appContext.sharedObjectRegistry.toNativeObject(jsObject)
expect(nativeObject).notTo(beNil())
}
it("registers shared object") {
let oldSize = appContext.sharedObjectRegistry.size
try runtime.eval("object = new expo.modules.TestModule.Counter(0)")
expect(appContext.sharedObjectRegistry.size) == oldSize + 1
}
it("calls function with owner") {
try runtime.eval([
"object = new expo.modules.TestModule.Counter(0)",
"object.increment(1)",
])
// no expectations, just checking if it doesn't fail
}
it("creates with initial value") {
let initialValue = Int.random(in: 1..<100)
let value = try runtime.eval([
"object = new expo.modules.TestModule.Counter(\(initialValue))",
"object.getValue()",
])
expect(value.kind) == .number
expect(value.getInt()) == initialValue
}
it("gets shared object value") {
let value = try runtime.eval([
"object = new expo.modules.TestModule.Counter(0)",
"object.getValue()",
])
expect(value.kind) == .number
expect(value.isNumber()) == true
}
it("changes shared object") {
try runtime.eval("object = new expo.modules.TestModule.Counter(0)")
let incrementBy = Int.random(in: 1..<100)
let value = try runtime.eval("object.getValue()").asInt()
let newValue = try runtime.eval([
"object.increment(\(incrementBy))",
"object.getValue()",
])
expect(newValue.kind) == .number
expect(newValue.getInt()) == value + incrementBy
}
it("gets value from the dynamic property") {
let initialValue = Int.random(in: 1..<100)
let value = try runtime.eval([
"object = new expo.modules.TestModule.Counter(\(initialValue))",
"object.currentValue"
])
expect(value.kind) == .number
expect(value.getInt()) == initialValue
}
it("initializes the shared object from native") {
let initialValue = Int.random(in: 1..<100)
let value = try runtime.eval("expo.modules.TestModule.newCounter(\(initialValue))")
expect(value.kind) == .object
expect(value.getObject().getProperty("currentValue").getInt()) == initialValue
}
}
describe("constructor error handling") {
let appContext = AppContext.create()
let runtime = try! appContext.runtime
beforeSuite {
class ErrorTestModule: Module {
func definition() -> ModuleDefinition {
Name("ErrorTest")
Class("FailingClass") {
Constructor { (shouldFail: Bool) in
if shouldFail {
throw TestCodedException()
}
return Counter(initialValue: 0)
}
Function("test") {
return "success"
}
}
}
}
appContext.moduleRegistry.register(moduleType: ErrorTestModule.self)
}
it("exceptions are in the correct format") {
expect {
try runtime.eval("new expo.modules.ErrorTest.FailingClass(true)")
}.to(throwError { (error: JavaScriptEvalException) in
let reason = error.param.userInfo["message"] as? String ?? ""
expect(reason).to(contain("Calling the 'constructor' function has failed"))
expect(reason).to(contain("→ Caused by:"))
expect(reason).to(contain("This is a test Exception with a code"))
})
}
it("check error codes from coded exceptions") {
let errorCode = try runtime.eval([
"try { new expo.modules.ErrorTest.FailingClass(true) } catch (error) { error.code }"
]).getString()
expect(errorCode) == "E_TEST_CODE"
}
it("succeeds when constructor does not throw") {
let result = try runtime.eval("new expo.modules.ErrorTest.FailingClass(false)")
expect(result.kind) == .object
expect(result.getObject().hasProperty("test")) == true
}
it("can call methods on successfully constructed objects") {
let result = try runtime.eval([
"obj = new expo.modules.ErrorTest.FailingClass(false)",
"obj.test()"
])
expect(result.getString()) == "success"
}
}
}
}
/**
A module that exposes a Counter class with an associated shared object class.
*/
fileprivate final class ModuleWithCounterClass: Module {
func definition() -> ModuleDefinition {
Name("TestModule")
Function("newCounter") { (initialValue: Int) in
return Counter(initialValue: initialValue)
}
Class(Counter.self) {
Constructor { (initialValue: Int) in
return Counter(initialValue: initialValue)
}
Function("increment") { (counter, value: Int) in
counter.increment(by: value)
}
Function("getValue") { counter in
return counter.currentValue
}
Property("currentValue") { counter in
return counter.currentValue
}
}
}
}
/**
A shared object class that stores some native value and can be used as an associated type of the JS class.
*/
fileprivate final class Counter: SharedObject {
var currentValue = 0
init(initialValue: Int = 0) {
self.currentValue = initialValue
}
func increment(by value: Int = 1) {
currentValue += value
}
}