1""" 2Tests for PyObjCTools.KeyValueCoding 3 4TODO: 5 - Accessing properties in superclass of ObjC hybrids (see also Foundation.test.test_keyvalue) 6 7NOTE: Testcases here should be synchronized with the Key-Value Coding tests 8in objc.test.test_keyvalue and Foundation.test.test_keyvalue. 9""" 10 11from PyObjCTools.KeyValueCoding import * 12from objc.test.keyvaluehelper import * 13import objc 14import unittest 15from objc.test import ctests 16from Foundation import * 17import datetime 18 19class KeyValueClass5 (object): 20 def __init__(self): 21 self.key3 = 3 22 self._key4 = u"4" 23 self.__private = u'private' 24 25 def addMultiple(self): 26 self.multiple = KeyValueClass5() 27 self.multiple.level2 = KeyValueClass5() 28 self.multiple.level2.level3 = KeyValueClass5() 29 self.multiple.level2.level3.keyA = u"hello" 30 self.multiple.level2.level3.keyB = u"world" 31 32 def getKey1(self): 33 return 1 34 35 def get_key2(self): 36 return 2 37 38 def setKey4(self, value): 39 self._key4 = value * 4 40 41 def set_key5(self, value): 42 self.key5 = value * 5 43 44 45class KeyValueClass6 (object): 46 __slots__ = (u'foo', ) 47 48 def __init__(self): 49 self.foo = u"foobar" 50 51 # Definition for property 'bar'. Use odd names for the methods 52 # because the KeyValue support recognizes the usual names. 53 def read_bar(self): 54 return self.foo + self.foo 55 56 def write_bar (self, value): 57 self.foo = value + value 58 59 bar = property(read_bar, write_bar) 60 61 roprop = property(lambda self: u"read-only") 62 63class KeyValueClass7 (NSObject): 64 def init(self): 65 self = super(KeyValueClass7, self).init() 66 self.key3 = 3 67 self._key4 = u"4" 68 self.__private = u'private' 69 return self 70 71 def addMultiple(self): 72 self.multiple = KeyValueClass5() 73 self.multiple.level2 = KeyValueClass5() 74 self.multiple.level2.level3 = KeyValueClass5() 75 self.multiple.level2.level3.keyA = u"hello" 76 self.multiple.level2.level3.keyB = u"world" 77 78 def getKey1(self): 79 return 1 80 81 def key2(self): 82 return 2 83 84 def setKey4_(self, value): 85 self._key4 = value * 4 86 87 def setKey5_(self, value): 88 self.key5 = value * 5 89 90 def keyM(self): 91 return u"m" 92 93class KeyValueClass8 (NSObject): 94 __slots__ = ('foo', ) 95 96 def init(self): 97 self = super(KeyValueClass8, self).init() 98 self.foo = u"foobar" 99 return self 100 101 # Definition for property 'bar'. Use odd names for the methods 102 # because the KeyValue support recognizes the usual names. 103 def read_bar(self): 104 return self.foo + self.foo 105 106 def write_bar (self, value): 107 self.foo = value + value 108 109 bar = property(read_bar, write_bar) 110 111 roprop = property(lambda self: u"read-only") 112 113 114 115class PyKeyValueCoding (unittest.TestCase): 116 def testNoPrivateVars(self): 117 # Private instance variables ('anObject.__value') are not accessible using 118 # key-value coding. 119 120 o = KeyValueClass5() 121 self.assertRaises(KeyError, getKey, o, u"private") 122 123 def testValueForKey(self): 124 o = KeyValueClass5() 125 o.addMultiple() 126 127 self.assertEquals(getKey(o, u"key1"), 1) 128 self.assertEquals(getKey(o, u"key2"), 2) 129 self.assertEquals(getKey(o, u"key3"), 3) 130 self.assertEquals(getKey(o, u"key4"), u"4") 131 self.assertEquals(getKey(o, u"multiple"), o.multiple) 132 133 self.assertRaises(KeyError, getKey, o, u"nokey") 134 135 def testValueForKey2(self): 136 o = KeyValueClass6() 137 138 self.assertEquals(getKey(o, u"foo"), u"foobar") 139 self.assertEquals(getKey(o, u"bar"), u"foobarfoobar") 140 self.assertEquals(getKey(o, u"roprop"), u"read-only") 141 142 def testValueForKeyPath(self): 143 o = KeyValueClass5() 144 o.addMultiple() 145 146 self.assertEquals(getKeyPath(o, u"multiple"), o.multiple) 147 self.assertEquals(getKeyPath(o, u"multiple.level2"), o.multiple.level2) 148 self.assertEquals(getKeyPath(o, u"multiple.level2.level3.keyA"), o.multiple.level2.level3.keyA) 149 self.assertEquals(getKeyPath(o, u"multiple.level2.level3.keyB"), o.multiple.level2.level3.keyB) 150 151 self.assertRaises(KeyError, getKeyPath, o, u"multiple.level2.nokey") 152 153 def testTakeValueForKey(self): 154 o = KeyValueClass5() 155 156 self.assertEquals(o.key3, 3) 157 setKey(o, u'key3', u'drie') 158 self.assertEquals(o.key3, u"drie") 159 160 self.assertEquals(o._key4, u"4") 161 setKey(o, u'key4', u'vier') 162 self.assertEquals(o._key4, u"viervierviervier") 163 164 o.key5 = 1 165 setKey(o, u'key5', u'V') 166 self.assertEquals(o.key5, u"VVVVV") 167 168 self.assert_(not hasattr(o, u'key9')) 169 setKey(o, u'key9', u'IX') 170 self.assert_(hasattr(o, u'key9')) 171 self.assertEquals(o.key9, u'IX') 172 173 def testTakeValueForKey2(self): 174 o = KeyValueClass6() 175 176 self.assertEquals(o.foo, u"foobar") 177 setKey(o, u'foo', u'FOO') 178 self.assertEquals(o.foo, u"FOO") 179 180 self.assertRaises(KeyError, setKey, o, u'key9', u'IX') 181 182 def testTakeValueForKeyPath(self): 183 o = KeyValueClass5() 184 o.addMultiple() 185 186 self.assertEquals(o.multiple.level2.level3.keyA, u"hello") 187 self.assertEquals(o.multiple.level2.level3.keyB, u"world") 188 189 setKeyPath(o, u"multiple.level2.level3.keyA", u"KeyAValue") 190 self.assertEquals(o.multiple.level2.level3.keyA, u"KeyAValue") 191 192 setKeyPath(o, u"multiple.level2.level3.keyB", 9.999) 193 self.assertEquals(o.multiple.level2.level3.keyB, 9.999) 194 195 196class OcKeyValueCoding (unittest.TestCase): 197 def testNoPrivateVars(self): 198 # Private instance variables ('anObject.__value') are not accessible using 199 # key-value coding. 200 201 o = KeyValueClass7.alloc().init() 202 self.assertRaises(KeyError, getKey, o, u"private") 203 204 def testArrayValueForKey(self): 205 o = KeyValueClass7.alloc().init() 206 o.addMultiple() 207 208 self.assertEquals(getKey(o, u"key1"), 1) 209 self.assertEquals(getKey(o, u"key2"), 2) 210 self.assertEquals(getKey(o, u"key3"), 3) 211 self.assertEquals(getKey(o, u"key4"), u"4") 212 self.assertEquals(getKey(o, u"multiple"), o.multiple) 213 214 self.assertEquals(o.valueForKey_(u"keyM"), u"m") 215 216 a = NSMutableArray.array() 217 a.addObject_(o) 218 a.addObject_({u"keyM": u"5"}) 219 a.addObject_(NSDictionary.dictionaryWithObject_forKey_(u"foo", u"keyM")) 220 b = NSMutableArray.arrayWithObjects_(u"m", u"5", u"foo", None) 221 222 223 # See Modules/objc/unittest.m for an explantion of this test 224 try: 225 ctests.TestArrayCoding() 226 arrayObservingWorks = True 227 except AssertionError: 228 arrayObservingWorks = False 229 230 if arrayObservingWorks: 231 self.assertEquals(a.valueForKey_(u"keyM"), b) 232 else: 233 self.assertRaises(KeyError, a.valueForKey_, u"keyM") 234 235 self.assertRaises(KeyError, getKey, o, u"nokey") 236 237 def testValueForKey2(self): 238 o = KeyValueClass8.alloc().init() 239 240 self.assertEquals(getKey(o, u"foo"), u"foobar") 241 self.assertEquals(getKey(o, u"bar"), u"foobarfoobar") 242 self.assertEquals(getKey(o, u"roprop"), u"read-only") 243 244 def testValueForKeyPath(self): 245 o = KeyValueClass7.alloc().init() 246 o.addMultiple() 247 248 self.assertEquals(getKeyPath(o, u"multiple"), o.multiple) 249 self.assertEquals(getKeyPath(o, u"multiple.level2"), o.multiple.level2) 250 self.assertEquals(getKeyPath(o, u"multiple.level2.level3.keyA"), o.multiple.level2.level3.keyA) 251 self.assertEquals(getKeyPath(o, u"multiple.level2.level3.keyB"), o.multiple.level2.level3.keyB) 252 253 self.assertRaises(KeyError, getKeyPath, o, u"multiple.level2.nokey") 254 255 def testTakeValueForKey(self): 256 o = KeyValueClass7.alloc().init() 257 258 self.assertEquals(o.key3, 3) 259 setKey(o, u'key3', u'drie') 260 self.assertEquals(o.key3, u"drie") 261 262 self.assertEquals(o._key4, u"4") 263 setKey(o, u'key4', u'vier') 264 self.assertEquals(o._key4, u"viervierviervier") 265 266 o.key5 = 1 267 setKey(o, u'key5', u'V') 268 self.assertEquals(o.key5, u"VVVVV") 269 270 self.assert_(not hasattr(o, u'key9')) 271 setKey(o, u'key9', u'IX') 272 self.assert_(hasattr(o, u'key9')) 273 self.assertEquals(o.key9, u'IX') 274 275 def testTakeValueForKey2(self): 276 o = KeyValueClass8.alloc().init() 277 278 self.assertEquals(o.foo, u"foobar") 279 setKey(o, u'foo', u'FOO') 280 self.assertEquals(o.foo, u"FOO") 281 282 self.assertRaises(KeyError, setKey, o, u'key9', u'IX') 283 284 def testTakeValueForKeyPath(self): 285 o = KeyValueClass7.alloc().init() 286 o.addMultiple() 287 288 self.assertEquals(o.multiple.level2.level3.keyA, u"hello") 289 self.assertEquals(o.multiple.level2.level3.keyB, u"world") 290 291 setKeyPath(o, u"multiple.level2.level3.keyA", u"KeyAValue") 292 self.assertEquals(o.multiple.level2.level3.keyA, u"KeyAValue") 293 294 setKeyPath(o, u"multiple.level2.level3.keyB", 9.999) 295 self.assertEquals(o.multiple.level2.level3.keyB, 9.999) 296 297class MethodsAsKeys (unittest.TestCase): 298 299 def testStrCap (self): 300 s = u"hello" 301 302 self.assertEquals(getKey(s, u'capitalize'), u"Hello") 303 304 305class AbstractKVCodingTest: 306 def testBaseValueForKey(self): 307 self.assertEquals(DirectString, 308 getKey( self.base, u"directString")) 309 self.assertEquals(IndirectString, 310 getKey( self.base, u"indirectString")) 311 self.assertEquals(DirectNumber, 312 getKey( self.base, u"directNumber")) 313 self.assertEquals(IndirectNumber, 314 getKey( self.base, u"indirectNumber")) 315 316 def testPathValueForKey(self): 317 self.assertEquals(DirectString, 318 getKeyPath( self.path, u"directHead.directString")) 319 self.assertEquals(DirectString, 320 getKeyPath( self.path, u"indirectHead.directString")) 321 self.assertEquals(IndirectString, 322 getKeyPath( self.path, u"directHead.indirectString")) 323 self.assertEquals(IndirectString, 324 getKeyPath( self.path, u"indirectHead.indirectString")) 325 self.assertEquals(DirectNumber, 326 getKeyPath( self.path, u"directHead.directNumber")) 327 self.assertEquals(DirectNumber, 328 getKeyPath( self.path, u"indirectHead.directNumber")) 329 self.assertEquals(IndirectNumber, 330 getKeyPath( self.path, u"directHead.indirectNumber")) 331 self.assertEquals(IndirectNumber, 332 getKeyPath( self.path, u"indirectHead.indirectNumber")) 333 334class TestObjCKVCoding(AbstractKVCodingTest, unittest.TestCase): 335 def setUp(self): 336 self.base = PyObjCTest_KVBaseClass.new() 337 self.path = PyObjCTest_KVPathClass.new() 338 339class TestPythonKVCoding(AbstractKVCodingTest, unittest.TestCase): 340 def setUp(self): 341 self.base = KVPyBase() 342 self.path = KVPyPath() 343 344class TestPythonSubObjCContainerCoding(AbstractKVCodingTest, unittest.TestCase): 345 def setUp(self): 346 self.base = KVPySubObjCBase.new() 347 self.path = KVPySubObjCPath.new() 348 349class TestPythonSubOverObjC(AbstractKVCodingTest, unittest.TestCase): 350 def setUp(self): 351 self.base = KVPySubOverObjCBase.new() 352 self.path = KVPySubOverObjCPath.new() 353 354 def testOverValueKey(self): 355 self.assertEquals(DirectString, 356 getKey( self.base, u"overDirectString")) 357 self.assertEquals(IndirectString, 358 getKey( self.base, u"overIndirectString")) 359 360 def testOverValueKeyPath(self): 361 self.assertEquals(DirectString, 362 getKeyPath( self.path, u"overDirectHead.directString")) 363 self.assertEquals(DirectString, 364 getKeyPath( self.path, u"overIndirectHead.directString")) 365 self.assertEquals(IndirectString, 366 getKeyPath( self.path, u"overDirectHead.indirectString")) 367 self.assertEquals(IndirectString, 368 getKeyPath( self.path, u"overIndirectHead.indirectString")) 369 370class Account(object): 371 def __init__(self, **kw): 372 self.__dict__.update(kw) 373 374class Transaction(object): 375 def __init__(self, **kw): 376 self.__dict__.update(kw) 377 378class PyObjCAccount(NSObject): 379 openingBalance = objc.ivar('openingBalance', 'd') 380 name = objc.ivar('name') 381 notes = objc.ivar('notes') 382 transactions = objc.ivar('transactions') 383 384 def __new__(cls, **kw): 385 self = cls.alloc().init() 386 for k, v in kw.iteritems(): 387 setattr(self, k, v) 388 return self 389 390class PyObjCTransaction(NSObject): 391 referenceNumber = objc.ivar('referenceNumber', 'I') 392 amount = objc.ivar('amount', 'd') 393 payee = objc.ivar('payee') 394 date = objc.ivar('date') 395 category = objc.ivar('category') 396 reconciled = objc.ivar(objc._C_BOOL) 397 398 def __new__(cls, **kw): 399 self = cls.alloc().init() 400 for k, v in kw.iteritems(): 401 setattr(self, k, v) 402 return self 403 404def makeAccounts(Account, Transaction): 405 return [ 406 Account( 407 openingBalance=10.0, 408 name=u'Alice', 409 notes=u'Alice notes', 410 transactions=[ 411 Transaction( 412 referenceNumber=1, 413 amount=20.0, 414 payee=u'Bob', 415 date=datetime.date(2005, 1, 1), 416 category=u'Tacos', 417 reconciled=True, 418 ), 419 Transaction( 420 referenceNumber=2, 421 amount=14.50, 422 payee=u'George', 423 date=datetime.date(2005, 1, 2), 424 category=u'Bagels', 425 reconciled=True, 426 ), 427 Transaction( 428 referenceNumber=3, 429 amount=250, 430 payee=u'Bill', 431 date=datetime.date(2005, 1, 3), 432 category=u'Tequila', 433 reconciled=True, 434 ), 435 ], 436 ), 437 Account( 438 openingBalance=10.0, 439 name=u'Bob', 440 notes=u'Bob notes', 441 transactions=[ 442 Transaction( 443 referenceNumber=4, 444 amount=25.0, 445 payee=u'Alice', 446 date=datetime.date(2005, 1, 4), 447 category=u'Beer', 448 reconciled=True, 449 ), 450 Transaction( 451 referenceNumber=5, 452 amount=60.0, 453 payee=u'George', 454 date=datetime.date(2005, 1, 5), 455 category=u'Book', 456 reconciled=True, 457 ), 458 Transaction( 459 referenceNumber=6, 460 amount=250, 461 payee=u'Bill', 462 date=datetime.date(2005, 1, 6), 463 category=u'Tequila', 464 reconciled=True, 465 ), 466 ], 467 ), 468 ] 469 470 471class TestArrayOperators(unittest.TestCase): 472 def setUp(self): 473 self.accounts = makeAccounts(Account, Transaction) 474 def testCount(self): 475 self.assertEquals( 476 getKeyPath(self, u'accounts.@count'), 2) 477 self.assertEquals( 478 getKeyPath(self, u'accounts.transactions.@count'), 2) 479 self.assertEquals( 480 getKeyPath(self.accounts, u'@count'), 2) 481 self.assertEquals( 482 getKeyPath(self.accounts[0], u'transactions.@count'), 3) 483 self.assertEquals( 484 getKeyPath(self.accounts[1], u'transactions.@count'), 3) 485 486 def testDistinctUnionOfArrays(self): 487 self.assertEquals( 488 getKeyPath( 489 self.accounts, 490 u'@distinctUnionOfArrays.transactions.payee'), 491 [u'Bob', u'George', u'Bill', u'Alice']) 492 self.assertEquals( 493 getKeyPath( 494 self.accounts, 495 u'@distinctUnionOfArrays.transactions.referenceNumber'), 496 [1, 2, 3, 4, 5, 6]) 497 self.assertEquals( 498 getKeyPath( 499 self.accounts, 500 u'@distinctUnionOfArrays.transactions.category'), 501 [u'Tacos', u'Bagels', u'Tequila', u'Beer', u'Book']) 502 503 def testDistinctUnionOfObjects(self): 504 alice = self.accounts[0] 505 v = getKeyPath(alice, u'transactions.@distinctUnionOfObjects.payee') 506 507 def assertSameUnion(a, b): 508 a = list(a) 509 b = list(b) 510 a.sort() 511 b.sort() 512 self.assertEquals(a, b) 513 514 515 516 517 assertSameUnion( 518 getKeyPath( 519 alice, 520 u'transactions.@distinctUnionOfObjects.payee'), 521 [u'Bill', u'Bob', u'George']) 522 assertSameUnion( 523 getKeyPath( 524 alice, 525 u'transactions.@distinctUnionOfObjects.referenceNumber'), 526 [1, 2, 3]) 527 assertSameUnion( 528 getKeyPath( 529 alice, 530 u'transactions.@distinctUnionOfObjects.reconciled'), 531 [True]) 532 533 def testMax(self): 534 alice = self.accounts[0] 535 self.assertEquals( 536 getKeyPath( 537 alice, 538 u'transactions.@max.date'), 539 datetime.date(2005, 1, 3)) 540 self.assertEquals( 541 getKeyPath( 542 alice, 543 u'transactions.@max.referenceNumber'), 544 3) 545 self.assertEquals( 546 getKeyPath( 547 alice, 548 u'transactions.@max.amount'), 549 250) 550 551 def testMin(self): 552 alice = self.accounts[0] 553 self.assertEquals( 554 getKeyPath( 555 alice, 556 u'transactions.@min.date'), 557 datetime.date(2005, 1, 1)) 558 self.assertEquals( 559 getKeyPath( 560 alice, 561 u'transactions.@min.referenceNumber'), 562 1) 563 self.assertEquals( 564 getKeyPath( 565 alice, 566 u'transactions.@min.amount'), 567 14.50) 568 569 def testSum(self): 570 alice = self.accounts[0] 571 self.assertEquals( 572 getKeyPath( 573 alice, 574 u'transactions.@sum.amount'), 575 20 + 14.50 + 250) 576 bob = self.accounts[1] 577 self.assertEquals( 578 getKeyPath( 579 bob, 580 u'transactions.@sum.amount'), 581 25 + 60 + 250) 582 583 def testUnionOfArrays(self): 584 self.assertEquals( 585 getKeyPath( 586 self.accounts, 587 u'@unionOfArrays.transactions.payee'), 588 [u'Bob', u'George', u'Bill', u'Alice', u'George', u'Bill']) 589 self.assertEquals( 590 getKeyPath( 591 self.accounts, 592 u'@unionOfArrays.transactions.referenceNumber'), 593 [1, 2, 3, 4, 5, 6]) 594 self.assertEquals( 595 getKeyPath( 596 self.accounts, 597 u'@unionOfArrays.transactions.category'), 598 [u'Tacos', u'Bagels', u'Tequila', u'Beer', u'Book', u'Tequila']) 599 600 def testUnionOfObjects(self): 601 alice = self.accounts[0] 602 self.assertEquals( 603 getKeyPath( 604 alice, 605 u'transactions.@unionOfObjects.payee'), 606 [u'Bob', u'George', u'Bill']) 607 self.assertEquals( 608 getKeyPath( 609 alice, 610 u'transactions.@unionOfObjects.referenceNumber'), 611 [1, 2, 3]) 612 self.assertEquals( 613 getKeyPath( 614 alice, 615 u'transactions.@unionOfObjects.reconciled'), 616 [True, True, True]) 617 618class TestPyObjCArrayOperators(TestArrayOperators): 619 def setUp(self): 620 self.accounts = makeAccounts(PyObjCAccount, PyObjCTransaction) 621 622 623if __name__ == "__main__": 624 unittest.main() 625