diff --git a/packages/tizen_bundle/CHANGELOG.md b/packages/tizen_bundle/CHANGELOG.md index 049f4a767..75e243e45 100644 --- a/packages/tizen_bundle/CHANGELOG.md +++ b/packages/tizen_bundle/CHANGELOG.md @@ -1,6 +1,9 @@ -## NEXT +## 0.1.3 * Update code format. +* Update integration test suite to 38 test cases. +* Fix `remove()` to be a no-op when the key does not exist. +* Fix `keys` getter to return an independent list per call. ## 0.1.2 diff --git a/packages/tizen_bundle/README.md b/packages/tizen_bundle/README.md index c8d22e4e8..ae2245cc0 100644 --- a/packages/tizen_bundle/README.md +++ b/packages/tizen_bundle/README.md @@ -10,7 +10,7 @@ To use this package, add `tizen_bundle` as a dependency in your `pubspec.yaml` f ```yaml depenedencies: - tizen_bundle: ^0.1.2 + tizen_bundle: ^0.1.3 ``` ### Adding content to a bundle diff --git a/packages/tizen_bundle/example/integration_test/bundle_test.dart b/packages/tizen_bundle/example/integration_test/bundle_test.dart deleted file mode 100644 index 88eab1e55..000000000 --- a/packages/tizen_bundle/example/integration_test/bundle_test.dart +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:tizen_bundle/tizen_bundle.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - testWidgets('can create a bundle', (WidgetTester _) async { - final Bundle bundle = Bundle(); - bundle['stringKey'] = 'stringValue'; - expect(bundle['stringKey'], 'stringValue'); - }); - - testWidgets('can create a bundle from another bundle', ( - WidgetTester _, - ) async { - final Bundle bundle = Bundle(); - bundle['stringKey'] = 'stringValue'; - - final Bundle newBundle = Bundle.fromBundle(bundle); - expect(newBundle['stringKey'], 'stringValue'); - }); - - testWidgets('can create a bundle from a map', (WidgetTester _) async { - final Bundle bundle = Bundle.fromMap({ - 'stringKey': 'stringValue', - 'stringsKey': ['value1', 'value2'], - 'bytesKey': Uint8List.fromList([0x03]), - }); - expect(bundle.length, 3); - expect(bundle['stringKey'], 'stringValue'); - expect(bundle['stringsKey'], ['value1', 'value2']); - expect(bundle['bytesKey'], Uint8List.fromList([0x03])); - }); - - testWidgets('returns all stored keys and values', (WidgetTester _) async { - final Bundle bundle = Bundle.fromMap({ - 'key1': 'value1', - 'key2': 'value2', - 'key3': 'value3', - }); - expect(bundle.keys, unorderedEquals(['key1', 'key2', 'key3'])); - expect( - bundle.values, - unorderedEquals(['value1', 'value2', 'value3']), - ); - }); - - testWidgets('checks whether the bundle is empty', (WidgetTester _) async { - final Bundle bundle = Bundle(); - bundle['stringKey'] = 'stringValue'; - expect(bundle.isNotEmpty, true); - - bundle.remove('stringKey'); - expect(bundle.isEmpty, true); - }); - - testWidgets('can add a Uint8List value', (WidgetTester _) async { - final Bundle bundle = Bundle(); - bundle['bytesKey'] = Uint8List(10); - - final Uint8List bytes = bundle['bytesKey']! as Uint8List; - expect(bytes.length, 10); - }); - - testWidgets('can add a List value', (WidgetTester _) async { - final Bundle bundle = Bundle(); - bundle['stringsKey'] = ['value1', 'value2']; - - final List strings = bundle['stringsKey']! as List; - expect(strings, ['value1', 'value2']); - }); - - testWidgets('can remove all entries', (WidgetTester _) async { - final Bundle bundle = Bundle(); - bundle['stringKey1'] = 'stringValue1'; - bundle['stringKey2'] = 'stringValue2'; - expect(bundle.length, 2); - - bundle.clear(); - expect(bundle.length, 0); - }); - - testWidgets('can remove key and its associated value', ( - WidgetTester _, - ) async { - final Bundle bundle = Bundle(); - bundle['stringKey'] = 'stringValue'; - expect(bundle.containsKey('stringKey'), true); - - bundle.remove('stringKey'); - expect(bundle.containsKey('stringKey'), false); - }); - - testWidgets('can encode and decode raw data', (WidgetTester _) async { - final Bundle bundle = Bundle(); - bundle['stringKey'] = 'stringValue'; - final String encoded = bundle.encode(); - final Bundle newBundle = Bundle.decode(encoded); - expect(newBundle['stringKey'], 'stringValue'); - }); -} diff --git a/packages/tizen_bundle/example/integration_test/tizen_bundle_test.dart b/packages/tizen_bundle/example/integration_test/tizen_bundle_test.dart new file mode 100644 index 000000000..62c310600 --- /dev/null +++ b/packages/tizen_bundle/example/integration_test/tizen_bundle_test.dart @@ -0,0 +1,434 @@ +// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:tizen_bundle/tizen_bundle.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // --------------------------------------------------------------------------- + // Bundle() — default constructor + // --------------------------------------------------------------------------- + group('Bundle() default constructor', () { + testWidgets('creates an empty bundle with length 0', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect(bundle.length, 0); + }); + + testWidgets('isEmpty returns true for a newly created bundle', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect(bundle.isEmpty, true); + }); + + testWidgets('isNotEmpty returns false for a newly created bundle', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect(bundle.isNotEmpty, false); + }); + + testWidgets('keys returns empty iterable for an empty bundle', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect(bundle.keys, isEmpty); + }); + }); + + // --------------------------------------------------------------------------- + // Bundle.fromMap — factory constructor + // --------------------------------------------------------------------------- + group('Bundle.fromMap', () { + testWidgets('creates an empty bundle from an empty map', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle.fromMap({}); + expect(bundle.length, 0); + expect(bundle.isEmpty, true); + }); + + testWidgets( + 'creates a bundle with String, List, and Uint8List entries', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle.fromMap({ + 'stringKey': 'stringValue', + 'stringsKey': ['value1', 'value2'], + 'bytesKey': Uint8List.fromList([0x03]), + }); + expect(bundle.length, 3); + expect(bundle['stringKey'], 'stringValue'); + expect(bundle['stringsKey'], ['value1', 'value2']); + expect(bundle['bytesKey'], Uint8List.fromList([0x03])); + }); + }); + + // --------------------------------------------------------------------------- + // Bundle.fromBundle — copy constructor independence + // --------------------------------------------------------------------------- + group('Bundle.fromBundle copy independence', () { + testWidgets( + 'mutating the original after copy does not affect the copy', + (WidgetTester _) async { + final Bundle original = Bundle(); + original['key'] = 'originalValue'; + + final Bundle copy = Bundle.fromBundle(original); + original['key'] = 'mutatedValue'; + + // copy should still hold the value at the time of duplication + expect(copy['key'], 'originalValue'); + }, + ); + + testWidgets( + 'copy has independent entries — adding to copy does not affect original', + ( + WidgetTester _, + ) async { + final Bundle original = Bundle(); + original['key'] = 'value'; + + final Bundle copy = Bundle.fromBundle(original); + copy['newKey'] = 'newValue'; + + expect(original.containsKey('newKey'), false); + }); + }); + + // --------------------------------------------------------------------------- + // Bundle.decode — constructor from encoded string + // --------------------------------------------------------------------------- + group('Bundle.decode', () { + testWidgets('round-trips a List value through encode/decode', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['stringsKey'] = ['alpha', 'beta', 'gamma']; + final String encoded = bundle.encode(); + final Bundle decoded = Bundle.decode(encoded); + expect(decoded['stringsKey'], ['alpha', 'beta', 'gamma']); + }); + + testWidgets('round-trips a Uint8List value through encode/decode', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['bytesKey'] = Uint8List.fromList([0x00, 0x7f, 0xff]); + final String encoded = bundle.encode(); + final Bundle decoded = Bundle.decode(encoded); + expect(decoded['bytesKey'], Uint8List.fromList([0x00, 0x7f, 0xff])); + }); + + testWidgets('round-trips multiple keys of mixed types', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['s'] = 'hello'; + bundle['ss'] = ['x', 'y']; + bundle['b'] = Uint8List.fromList([1, 2, 3]); + final String encoded = bundle.encode(); + final Bundle decoded = Bundle.decode(encoded); + expect(decoded.length, 3); + expect(decoded['s'], 'hello'); + expect(decoded['ss'], ['x', 'y']); + expect(decoded['b'], Uint8List.fromList([1, 2, 3])); + }); + }); + + // --------------------------------------------------------------------------- + // operator[] — get + // --------------------------------------------------------------------------- + group('operator[] (get)', () { + testWidgets('returns null for a missing key', (WidgetTester _) async { + final Bundle bundle = Bundle(); + expect(bundle['nonExistentKey'], isNull); + }); + + testWidgets('returns null when key is null', (WidgetTester _) async { + final Bundle bundle = Bundle(); + bundle['someKey'] = 'someValue'; + // The implementation accepts Object? key and returns null for null. + expect(bundle[null], isNull); + }); + + testWidgets('returns the correct value after key is overwritten', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['key'] = 'first'; + bundle['key'] = 'second'; + expect(bundle['key'], 'second'); + }); + + testWidgets( + 'same read-only call twice returns identical results (idempotency)', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['key'] = 'value'; + expect(bundle['key'], bundle['key']); + }); + }); + + // --------------------------------------------------------------------------- + // operator[]= — set (error path) + // --------------------------------------------------------------------------- + group('operator[]= (set)', () { + testWidgets('throws ArgumentError for an unsupported value type', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect( + () => bundle['key'] = 42, + throwsA(isA()), + ); + }); + + testWidgets('stores an empty string value', (WidgetTester _) async { + final Bundle bundle = Bundle(); + bundle['emptyString'] = ''; + expect(bundle['emptyString'], ''); + }); + + testWidgets('stores a single-element List', (WidgetTester _) async { + final Bundle bundle = Bundle(); + bundle['single'] = ['only']; + expect(bundle['single'], ['only']); + }); + + testWidgets('stores an empty Uint8List', (WidgetTester _) async { + final Bundle bundle = Bundle(); + bundle['emptyBytes'] = Uint8List(0); + final Uint8List result = bundle['emptyBytes']! as Uint8List; + expect(result.length, 0); + }); + + testWidgets('preserves full byte range 0x00–0xff in Uint8List', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + final Uint8List allBytes = + Uint8List.fromList(List.generate(256, (int i) => i)); + bundle['allBytes'] = allBytes; + final Uint8List result = bundle['allBytes']! as Uint8List; + expect(result, allBytes); + }); + }); + + // --------------------------------------------------------------------------- + // remove() + // --------------------------------------------------------------------------- + group('remove()', () { + testWidgets('remove(null) is a no-op and does not throw', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['key'] = 'value'; + expect(() => bundle.remove(null), returnsNormally); + expect(bundle.length, 1); + }); + + testWidgets('removing a non-existent key is a no-op', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect(() => bundle.remove('doesNotExist'), returnsNormally); + }); + + testWidgets('add then remove then verify gone (state transition)', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['key'] = 'value'; + expect(bundle.containsKey('key'), true); + bundle.remove('key'); + expect(bundle.containsKey('key'), false); + expect(bundle['key'], isNull); + }); + }); + + // --------------------------------------------------------------------------- + // clear() + // --------------------------------------------------------------------------- + group('clear()', () { + testWidgets('clear() on an already-empty bundle is idempotent', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle.clear(); + expect(bundle.length, 0); + expect(bundle.isEmpty, true); + }); + + testWidgets('clear() removes all entries including mixed types', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['s'] = 'hello'; + bundle['ss'] = ['a', 'b']; + bundle['b'] = Uint8List.fromList([1]); + bundle.clear(); + expect(bundle.isEmpty, true); + expect(bundle.keys, isEmpty); + }); + }); + + // --------------------------------------------------------------------------- + // length / isEmpty / isNotEmpty — state after modifications + // --------------------------------------------------------------------------- + group('length, isEmpty, isNotEmpty', () { + testWidgets('length reflects current entry count correctly', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect(bundle.length, 0); + bundle['k1'] = 'v1'; + expect(bundle.length, 1); + bundle['k2'] = 'v2'; + expect(bundle.length, 2); + bundle.remove('k1'); + expect(bundle.length, 1); + }); + + testWidgets('isNotEmpty becomes true after adding an entry', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect(bundle.isNotEmpty, false); + bundle['key'] = 'value'; + expect(bundle.isNotEmpty, true); + }); + + testWidgets('isEmpty becomes true after removing the last entry', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['key'] = 'value'; + expect(bundle.isEmpty, false); + bundle.remove('key'); + expect(bundle.isEmpty, true); + }); + }); + + // --------------------------------------------------------------------------- + // Inherited MapMixin members + // --------------------------------------------------------------------------- + group('inherited MapMixin members', () { + testWidgets('containsKey returns true for existing key', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['key'] = 'value'; + expect(bundle.containsKey('key'), true); + }); + + testWidgets('containsKey returns false for missing key', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + expect(bundle.containsKey('missing'), false); + }); + + testWidgets('keys of different bundles are independent', ( + WidgetTester _, + ) async { + final Bundle bundle1 = Bundle(); + bundle1['a'] = 'v1'; + final Iterable keys1 = bundle1.keys; + + final Bundle bundle2 = Bundle(); + bundle2['b'] = 'v2'; + bundle2.keys; + + expect(keys1, equals(['a'])); + }); + + testWidgets('keys and values return all stored entries', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle.fromMap({ + 'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3', + }); + expect(bundle.keys, unorderedEquals(['key1', 'key2', 'key3'])); + expect( + bundle.values, + unorderedEquals(['value1', 'value2', 'value3']), + ); + }); + + testWidgets('containsValue returns true for existing string value', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['key'] = 'needle'; + expect(bundle.containsValue('needle'), true); + }); + + testWidgets('containsValue returns false for absent value', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['key'] = 'value'; + expect(bundle.containsValue('absent'), false); + }); + + testWidgets('entries exposes key-value pairs correctly', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['k'] = 'v'; + final MapEntry entry = bundle.entries.single; + expect(entry.key, 'k'); + expect(entry.value, 'v'); + }); + + testWidgets('forEach iterates over all entries', (WidgetTester _) async { + final Bundle bundle = Bundle(); + bundle['a'] = '1'; + bundle['b'] = '2'; + final Map collected = {}; + bundle.forEach((String key, Object value) { + collected[key] = value; + }); + expect(collected.length, 2); + expect(collected['a'], '1'); + expect(collected['b'], '2'); + }); + + testWidgets('addAll adds all entries from a map', (WidgetTester _) async { + final Bundle bundle = Bundle(); + bundle.addAll({ + 'x': 'valueX', + 'y': 'valueY', + }); + expect(bundle.length, 2); + expect(bundle['x'], 'valueX'); + expect(bundle['y'], 'valueY'); + }); + + testWidgets('putIfAbsent inserts only when key is absent', ( + WidgetTester _, + ) async { + final Bundle bundle = Bundle(); + bundle['existing'] = 'original'; + + final Object result = bundle.putIfAbsent('existing', () => 'new'); + expect(result, 'original'); + expect(bundle['existing'], 'original'); + + final Object inserted = bundle.putIfAbsent('fresh', () => 'inserted'); + expect(inserted, 'inserted'); + expect(bundle['fresh'], 'inserted'); + }); + }); +} diff --git a/packages/tizen_bundle/lib/tizen_bundle.dart b/packages/tizen_bundle/lib/tizen_bundle.dart index 42814deee..68f2e2164 100644 --- a/packages/tizen_bundle/lib/tizen_bundle.dart +++ b/packages/tizen_bundle/lib/tizen_bundle.dart @@ -51,7 +51,7 @@ class Bundle extends MapMixin { (Pointer bundle) => tizen.bundle_free(bundle), ); - static final List _keys = []; + static List? _currentKeys; static void _bundleIteratorCallback( Pointer key, @@ -59,19 +59,21 @@ class Bundle extends MapMixin { Pointer keyval, Pointer userData, ) { - _keys.add(key.toDartString()); + _currentKeys!.add(key.toDartString()); } /// The keys of this. @override Iterable get keys { - _keys.clear(); + final List keys = []; + _currentKeys = keys; tizen.bundle_foreach( _handle, Pointer.fromFunction(_bundleIteratorCallback), nullptr, ); - return _keys; + _currentKeys = null; + return keys; } @override @@ -137,7 +139,8 @@ class Bundle extends MapMixin { final String keyName = key as String; return tizen.bundle_del(_handle, keyName.toNativeChar(allocator: arena)); }); - if (ret != bundle_error_e.BUNDLE_ERROR_NONE) { + if (ret != bundle_error_e.BUNDLE_ERROR_NONE && + ret != bundle_error_e.BUNDLE_ERROR_KEY_NOT_AVAILABLE) { _throwException(ret); } } diff --git a/packages/tizen_bundle/pubspec.yaml b/packages/tizen_bundle/pubspec.yaml index ee02ea533..8ab6e7b35 100644 --- a/packages/tizen_bundle/pubspec.yaml +++ b/packages/tizen_bundle/pubspec.yaml @@ -2,7 +2,7 @@ name: tizen_bundle description: Tizen data bundle APIs. homepage: https://github.com/flutter-tizen/plugins repository: https://github.com/flutter-tizen/plugins/tree/master/packages/tizen_bundle -version: 0.1.2 +version: 0.1.3 environment: sdk: ">=3.1.0 <4.0.0"