An amazing project that generates micro reports from tournament results
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

328 lines
15 KiB

////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
import XCTest
import RealmSwift
var pkCounter = 0
func nextPrimaryKey() -> Int {
pkCounter += 1
return pkCounter
}
class KVOObject: Object {
@objc dynamic var pk = nextPrimaryKey() // primary key for equality
@objc dynamic var ignored: Int = 0
@objc dynamic var boolCol: Bool = false
@objc dynamic var int8Col: Int8 = 1
@objc dynamic var int16Col: Int16 = 2
@objc dynamic var int32Col: Int32 = 3
@objc dynamic var int64Col: Int64 = 4
@objc dynamic var floatCol: Float = 5
@objc dynamic var doubleCol: Double = 6
@objc dynamic var stringCol: String = ""
@objc dynamic var binaryCol: Data = Data()
@objc dynamic var dateCol: Date = Date(timeIntervalSince1970: 0)
@objc dynamic var objectCol: KVOObject?
let arrayCol = List<KVOObject>()
let optIntCol = RealmOptional<Int>()
let optFloatCol = RealmOptional<Float>()
let optDoubleCol = RealmOptional<Double>()
let optBoolCol = RealmOptional<Bool>()
@objc dynamic var optStringCol: String?
@objc dynamic var optBinaryCol: Data?
@objc dynamic var optDateCol: Date?
let arrayBool = List<Bool>()
let arrayInt8 = List<Int8>()
let arrayInt16 = List<Int16>()
let arrayInt32 = List<Int32>()
let arrayInt64 = List<Int64>()
let arrayFloat = List<Float>()
let arrayDouble = List<Double>()
let arrayString = List<String>()
let arrayBinary = List<Data>()
let arrayDate = List<Date>()
let arrayOptBool = List<Bool?>()
let arrayOptInt8 = List<Int8?>()
let arrayOptInt16 = List<Int16?>()
let arrayOptInt32 = List<Int32?>()
let arrayOptInt64 = List<Int64?>()
let arrayOptFloat = List<Float?>()
let arrayOptDouble = List<Double?>()
let arrayOptString = List<String?>()
let arrayOptBinary = List<Data?>()
let arrayOptDate = List<Date?>()
override class func primaryKey() -> String { return "pk" }
override class func ignoredProperties() -> [String] { return ["ignored"] }
}
// Most of the testing of KVO functionality is done in the obj-c tests
// These tests just verify that it also works on Swift types
class KVOTests: TestCase {
var realm: Realm! = nil
override func setUp() {
super.setUp()
realm = try! Realm()
realm.beginWrite()
}
override func tearDown() {
realm.cancelWrite()
realm = nil
super.tearDown()
}
var changeDictionary: [NSKeyValueChangeKey: Any]?
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
changeDictionary = change
}
// swiftlint:disable:next cyclomatic_complexity
func observeChange<T: Equatable>(_ obj: KVOObject, _ key: String, _ old: T?, _ new: T?,
fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) {
let kvoOptions: NSKeyValueObservingOptions = [.old, .new]
obj.addObserver(self, forKeyPath: key, options: kvoOptions, context: nil)
block()
obj.removeObserver(self, forKeyPath: key)
XCTAssert(changeDictionary != nil, "Did not get a notification", file: fileName, line: lineNumber)
guard changeDictionary != nil else { return }
let actualOld = changeDictionary![.oldKey]! as? T
let actualNew = changeDictionary![.newKey]! as? T
XCTAssert(old == actualOld,
"Old value: expected \(String(describing: old)), got \(String(describing: actualOld))",
file: fileName, line: lineNumber)
XCTAssert(new == actualNew,
"New value: expected \(String(describing: new)), got \(String(describing: actualNew))",
file: fileName, line: lineNumber)
changeDictionary = nil
}
#if swift(>=3.2)
func observeChange<T: Equatable>(_ obj: KVOObject, _ keyPath: KeyPath<KVOObject, T>, _ old: Any?, _ new: Any?,
fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) {
let kvoOptions: NSKeyValueObservingOptions = [.old, .new]
var gotNotification = false
let observation = obj.observe(keyPath, options: kvoOptions) { _, change in
if let old = old {
XCTAssertEqual(change.oldValue, (old as! T), file: fileName, line: lineNumber)
} else {
XCTAssertNil(change.oldValue, file: fileName, line: lineNumber)
}
if let new = new {
XCTAssertEqual(change.newValue, (new as! T), file: fileName, line: lineNumber)
} else {
XCTAssertNil(change.newValue, file: fileName, line: lineNumber)
}
gotNotification = true
}
block()
observation.invalidate()
XCTAssertTrue(gotNotification, file: fileName, line: lineNumber)
}
#endif
func observeListChange(_ obj: NSObject, _ key: String, _ kind: NSKeyValueChange, _ indexes: NSIndexSet = NSIndexSet(index: 0),
fileName: StaticString = #file, lineNumber: UInt = #line, _ block: () -> Void) {
obj.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil)
block()
obj.removeObserver(self, forKeyPath: key)
XCTAssert(changeDictionary != nil, "Did not get a notification", file: fileName, line: lineNumber)
guard changeDictionary != nil else { return }
let actualKind = NSKeyValueChange(rawValue: (changeDictionary![NSKeyValueChangeKey.kindKey] as! NSNumber).uintValue)!
let actualIndexes = changeDictionary![NSKeyValueChangeKey.indexesKey]! as! NSIndexSet
XCTAssert(actualKind == kind, "Change kind: expected \(kind), got \(actualKind)", file: fileName,
line: lineNumber)
XCTAssert(actualIndexes.isEqual(indexes), "Changed indexes: expected \(indexes), got \(actualIndexes)",
file: fileName, line: lineNumber)
changeDictionary = nil
}
func getObject(_ obj: KVOObject) -> (KVOObject, KVOObject) {
return (obj, obj)
}
// Actual tests follow
func testAllPropertyTypes() {
let (obj, obs) = getObject(KVOObject())
observeChange(obs, "boolCol", false, true) { obj.boolCol = true }
#if swift(>=3.2)
observeChange(obs, "int8Col", 1 as Int8, 10) { obj.int8Col = 10 }
observeChange(obs, "int16Col", 2 as Int16, 10) { obj.int16Col = 10 }
observeChange(obs, "int32Col", 3 as Int32, 10) { obj.int32Col = 10 }
observeChange(obs, "int64Col", 4 as Int64, 10) { obj.int64Col = 10 }
#else
observeChange(obs, "int8Col", 1, 10) { obj.int8Col = 10 }
observeChange(obs, "int16Col", 2, 10) { obj.int16Col = 10 }
observeChange(obs, "int32Col", 3, 10) { obj.int32Col = 10 }
observeChange(obs, "int64Col", 4, 10) { obj.int64Col = 10 }
#endif
observeChange(obs, "floatCol", 5 as Float, 10) { obj.floatCol = 10 }
observeChange(obs, "doubleCol", 6 as Double, 10) { obj.doubleCol = 10 }
observeChange(obs, "stringCol", "", "abc") { obj.stringCol = "abc" }
observeChange(obs, "objectCol", nil, obj) { obj.objectCol = obj }
let data = "abc".data(using: String.Encoding.utf8, allowLossyConversion: false)!
observeChange(obs, "binaryCol", Data(), data) { obj.binaryCol = data }
let date = Date(timeIntervalSince1970: 1)
observeChange(obs, "dateCol", Date(timeIntervalSince1970: 0), date) { obj.dateCol = date }
observeListChange(obs, "arrayCol", .insertion) { obj.arrayCol.append(obj) }
observeListChange(obs, "arrayCol", .removal) { obj.arrayCol.removeAll() }
observeChange(obs, "optIntCol", nil, 10) { obj.optIntCol.value = 10 }
observeChange(obs, "optFloatCol", nil, 10.0) { obj.optFloatCol.value = 10 }
observeChange(obs, "optDoubleCol", nil, 10.0) { obj.optDoubleCol.value = 10 }
observeChange(obs, "optBoolCol", nil, true) { obj.optBoolCol.value = true }
observeChange(obs, "optStringCol", nil, "abc") { obj.optStringCol = "abc" }
observeChange(obs, "optBinaryCol", nil, data) { obj.optBinaryCol = data }
observeChange(obs, "optDateCol", nil, date) { obj.optDateCol = date }
observeChange(obs, "optIntCol", 10, nil) { obj.optIntCol.value = nil }
observeChange(obs, "optFloatCol", 10.0, nil) { obj.optFloatCol.value = nil }
observeChange(obs, "optDoubleCol", 10.0, nil) { obj.optDoubleCol.value = nil }
observeChange(obs, "optBoolCol", true, nil) { obj.optBoolCol.value = nil }
observeChange(obs, "optStringCol", "abc", nil) { obj.optStringCol = nil }
observeChange(obs, "optBinaryCol", data, nil) { obj.optBinaryCol = nil }
observeChange(obs, "optDateCol", date, nil) { obj.optDateCol = nil }
observeListChange(obs, "arrayBool", .insertion) { obj.arrayBool.append(true); }
observeListChange(obs, "arrayInt8", .insertion) { obj.arrayInt8.append(10); }
observeListChange(obs, "arrayInt16", .insertion) { obj.arrayInt16.append(10); }
observeListChange(obs, "arrayInt32", .insertion) { obj.arrayInt32.append(10); }
observeListChange(obs, "arrayInt64", .insertion) { obj.arrayInt64.append(10); }
observeListChange(obs, "arrayFloat", .insertion) { obj.arrayFloat.append(10); }
observeListChange(obs, "arrayDouble", .insertion) { obj.arrayDouble.append(10); }
observeListChange(obs, "arrayString", .insertion) { obj.arrayString.append("abc"); }
observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.append(true); }
observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.append(10); }
observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.append(10); }
observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.append(10); }
observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.append(10); }
observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.append(10); }
observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.append(10); }
observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.append("abc"); }
observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.append(data); }
observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.append(date); }
observeListChange(obs, "arrayOptBool", .insertion) { obj.arrayOptBool.insert(nil, at: 0); }
observeListChange(obs, "arrayOptInt8", .insertion) { obj.arrayOptInt8.insert(nil, at: 0); }
observeListChange(obs, "arrayOptInt16", .insertion) { obj.arrayOptInt16.insert(nil, at: 0); }
observeListChange(obs, "arrayOptInt32", .insertion) { obj.arrayOptInt32.insert(nil, at: 0); }
observeListChange(obs, "arrayOptInt64", .insertion) { obj.arrayOptInt64.insert(nil, at: 0); }
observeListChange(obs, "arrayOptFloat", .insertion) { obj.arrayOptFloat.insert(nil, at: 0); }
observeListChange(obs, "arrayOptDouble", .insertion) { obj.arrayOptDouble.insert(nil, at: 0); }
observeListChange(obs, "arrayOptString", .insertion) { obj.arrayOptString.insert(nil, at: 0); }
observeListChange(obs, "arrayOptDate", .insertion) { obj.arrayOptDate.insert(nil, at: 0); }
observeListChange(obs, "arrayOptBinary", .insertion) { obj.arrayOptBinary.insert(nil, at: 0); }
if obs.realm == nil {
return
}
observeChange(obs, "invalidated", false, true) {
self.realm.delete(obj)
}
let (obj2, obs2) = getObject(KVOObject())
observeChange(obs2, "arrayCol.invalidated", false, true) {
self.realm.delete(obj2)
}
}
#if swift(>=3.2)
func testTypedObservation() {
let (obj, obs) = getObject(KVOObject())
observeChange(obs, \.boolCol, false, true) { obj.boolCol = true }
observeChange(obs, \.int8Col, 1 as Int8, 10 as Int8) { obj.int8Col = 10 }
observeChange(obs, \.int16Col, 2 as Int16, 10 as Int16) { obj.int16Col = 10 }
observeChange(obs, \.int32Col, 3 as Int32, 10 as Int32) { obj.int32Col = 10 }
observeChange(obs, \.int64Col, 4 as Int64, 10 as Int64) { obj.int64Col = 10 }
observeChange(obs, \.floatCol, 5 as Float, 10 as Float) { obj.floatCol = 10 }
observeChange(obs, \.doubleCol, 6 as Double, 10 as Double) { obj.doubleCol = 10 }
observeChange(obs, \.stringCol, "", "abc") { obj.stringCol = "abc" }
let data = "abc".data(using: String.Encoding.utf8, allowLossyConversion: false)!
observeChange(obs, \.binaryCol, Data(), data) { obj.binaryCol = data }
let date = Date(timeIntervalSince1970: 1)
observeChange(obs, \.dateCol, Date(timeIntervalSince1970: 0), date) { obj.dateCol = date }
#if swift(>=3.4) && (swift(>=4.1.50) || !swift(>=4))
observeChange(obs, \.objectCol, nil, obj) { obj.objectCol = obj }
observeChange(obs, \.optStringCol, nil, "abc") { obj.optStringCol = "abc" }
observeChange(obs, \.optBinaryCol, nil, data) { obj.optBinaryCol = data }
observeChange(obs, \.optDateCol, nil, date) { obj.optDateCol = date }
observeChange(obs, \.optStringCol, "abc", nil) { obj.optStringCol = nil }
observeChange(obs, \.optBinaryCol, data, nil) { obj.optBinaryCol = nil }
observeChange(obs, \.optDateCol, date, nil) { obj.optDateCol = nil }
#endif
if obs.realm == nil {
return
}
observeChange(obs, \.isInvalidated, false, true) {
self.realm.delete(obj)
}
}
#endif
func testReadSharedSchemaFromObservedObject() {
let obj = KVOObject()
obj.addObserver(self, forKeyPath: "boolCol", options: [.old, .new], context: nil)
XCTAssertEqual(type(of: obj).sharedSchema(), KVOObject.sharedSchema())
obj.removeObserver(self, forKeyPath: "boolCol")
}
}
class KVOPersistedTests: KVOTests {
override func getObject(_ obj: KVOObject) -> (KVOObject, KVOObject) {
realm.add(obj)
return (obj, obj)
}
}
class KVOMultipleAccessorsTests: KVOTests {
override func getObject(_ obj: KVOObject) -> (KVOObject, KVOObject) {
realm.add(obj)
return (obj, realm.object(ofType: KVOObject.self, forPrimaryKey: obj.pk)!)
}
}