blob: a00cc9a7ba6934f5cd31b313fffbf74337528c21 [file] [edit]
// 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);
}
});
});
});
}