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