| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // Objective C support is only available on mac. | |
| @TestOn('mac-os') | |
| library; | |
| import 'package:ffi/ffi.dart'; | |
| import 'package:objective_c/objective_c.dart'; | |
| import 'package:test/test.dart'; | |
| import 'util.dart'; | |
| void main() { | |
| group('NSDictionary', () { | |
| test('of', () { | |
| final obj1 = 'obj1'.toNSString(); | |
| final obj2 = 'obj2'.toNSString(); | |
| final obj3 = 'obj3'.toNSString(); | |
| final obj4 = 'obj4'.toNSString(); | |
| final obj5 = 'obj5'.toNSString(); | |
| final obj6 = 'obj6'.toNSString(); | |
| final dict = NSDictionary.of({ | |
| obj1: obj2, | |
| obj3: obj4, | |
| obj5: obj6, | |
| }).asDart(); | |
| expect(dict.length, 3); | |
| expect(dict[obj1], obj2); | |
| expect(dict[obj3], obj4); | |
| expect(dict[obj5], obj6); | |
| // Keys are copied, so compare the string values. | |
| final actualKeys = <String>[]; | |
| for (final key in dict.keys) { | |
| actualKeys.add(NSString.as(key).toDartString()); | |
| } | |
| expect(actualKeys, unorderedEquals(['obj1', 'obj3', 'obj5'])); | |
| // Values are stored by reference, so we can compare the actual objects. | |
| final actualValues = <ObjCObject>[]; | |
| for (final value in dict.values) { | |
| actualValues.add(value); | |
| } | |
| expect(actualValues, unorderedEquals([obj2, obj4, obj6])); | |
| }); | |
| test('immutable', () { | |
| final obj1 = 'obj1'.toNSString(); | |
| final obj2 = 'obj2'.toNSString(); | |
| final obj3 = 'obj3'.toNSString(); | |
| final obj4 = 'obj4'.toNSString(); | |
| final obj5 = 'obj5'.toNSString(); | |
| final obj6 = 'obj6'.toNSString(); | |
| // NSDictionary.of actually returns a NSMutableDictionary, so our | |
| // immutability tests wouldn't actually work. So convert it to a real | |
| // NSDictionary using an ObjC constructor. | |
| final dict = NSDictionary.dictionaryWithDictionary( | |
| NSDictionary.of({obj1: obj2, obj3: obj4, obj5: obj6}), | |
| ).asDart(); | |
| expect(() => dict[obj3] = obj1, throwsUnsupportedError); | |
| expect(dict.clear, throwsUnsupportedError); | |
| expect(() => dict.remove(obj1), throwsUnsupportedError); | |
| }); | |
| test('MapBase mixin', () { | |
| final obj1 = 'obj1'.toNSString(); | |
| final obj2 = 'obj2'.toNSString(); | |
| final obj3 = 'obj3'.toNSString(); | |
| final obj4 = 'obj4'.toNSString(); | |
| final obj5 = 'obj5'.toNSString(); | |
| final obj6 = 'obj6'.toNSString(); | |
| final dict = NSDictionary.of({ | |
| obj1: obj2, | |
| obj3: obj4, | |
| obj5: obj6, | |
| }).asDart(); | |
| expect(dict.isNotEmpty, isTrue); | |
| expect(dict.containsKey(obj1), isTrue); | |
| expect(dict.containsKey(obj4), isFalse); | |
| expect(dict.containsValue(obj2), isTrue); | |
| expect(dict.containsValue(obj3), isFalse); | |
| expect( | |
| dict.map((key, value) => MapEntry<ObjCObject, ObjCObject>(value, key)), | |
| {obj2: obj1, obj4: obj3, obj6: obj5}, | |
| ); | |
| expect( | |
| dict.keys.map((key) => NSString.as(key).toDartString()).toList(), | |
| unorderedEquals(['obj1', 'obj3', 'obj5']), | |
| ); | |
| expect(dict.values.toList(), unorderedEquals([obj2, obj4, obj6])); | |
| }); | |
| test('ref counting', () async { | |
| await using((arena) async { | |
| final trackers = <ReferenceTracker>[]; | |
| Map<NSCopying, ObjCObject>? map; | |
| autoReleasePool(() { | |
| // The dictionary key has to implement NSCopying. NSString is used in | |
| // the other tests because it's easy to construct. But it isn't ref | |
| // counted in the same way as other objects, so here we use NSArray. | |
| final obj1 = NSArray.of(['apple'.toNSString()]); | |
| final obj2 = NSObject(); | |
| final obj3 = NSArray.of(['banana'.toNSString()]); | |
| final obj4 = NSObject(); | |
| final obj5 = NSArray.of(['carrot'.toNSString()]); | |
| final obj6 = NSObject(); | |
| final objects = {obj1: obj2, obj3: obj4, obj5: obj6}; | |
| final objCMap = NSDictionary.of(objects); | |
| map = objCMap.asDart(); | |
| for (final o in map!.keys) { | |
| final keyTracker = ReferenceTracker(arena); | |
| keyTracker.track(o); | |
| trackers.add(keyTracker); | |
| } | |
| for (final o in map!.values) { | |
| final valueTracker = ReferenceTracker(arena); | |
| valueTracker.track(o); | |
| trackers.add(valueTracker); | |
| } | |
| final objCMapTracker = ReferenceTracker(arena); | |
| objCMapTracker.track(objCMap); | |
| trackers.add(objCMapTracker); | |
| for (final t in trackers) { | |
| expect(t.isAlive, true); | |
| } | |
| }); | |
| doGC(); | |
| await Future<void>.delayed(Duration.zero); | |
| doGC(); | |
| for (final t in trackers) { | |
| expect(t.isAlive, true); | |
| } | |
| map = null; | |
| doGC(); | |
| await Future<void>.delayed(Duration.zero); | |
| doGC(); | |
| for (final t in trackers) { | |
| expect(t.isAlive, false); | |
| } | |
| }); | |
| }); | |
| }); | |
| } |