From 27b6b2e7951949eac3d4afb08aa2859912166b6b Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Fri, 10 Jul 2026 15:38:46 +0200 Subject: [PATCH 1/2] test(yet_another_json_isolate): add full coverage test suite --- .../yet_another_json_isolate_io_test.dart | 21 ++ .../test/yet_another_json_isolate_test.dart | 193 ++++++++++++++++-- 2 files changed, 200 insertions(+), 14 deletions(-) create mode 100644 packages/yet_another_json_isolate/test/yet_another_json_isolate_io_test.dart diff --git a/packages/yet_another_json_isolate/test/yet_another_json_isolate_io_test.dart b/packages/yet_another_json_isolate/test/yet_another_json_isolate_io_test.dart new file mode 100644 index 000000000..8c47254fb --- /dev/null +++ b/packages/yet_another_json_isolate/test/yet_another_json_isolate_io_test.dart @@ -0,0 +1,21 @@ +@TestOn('vm') +library; + +import 'package:test/test.dart'; +import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; + +void main() { + group('io implementation', () { + test('throws when initialize is called twice', () async { + final isolate = YAJsonIsolate(); + await isolate.initialize(); + addTearDown(isolate.dispose); + expect(isolate.initialize(), throwsA(isA())); + }); + + test('exposes the provided debug name', () { + final isolate = YAJsonIsolate(debugName: 'my-isolate'); + expect(isolate.debugName, 'my-isolate'); + }); + }); +} diff --git a/packages/yet_another_json_isolate/test/yet_another_json_isolate_test.dart b/packages/yet_another_json_isolate/test/yet_another_json_isolate_test.dart index 7529d8930..b741f1b36 100644 --- a/packages/yet_another_json_isolate/test/yet_another_json_isolate_test.dart +++ b/packages/yet_another_json_isolate/test/yet_another_json_isolate_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:test/test.dart'; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; @@ -6,7 +8,8 @@ const _jsonMap = {'a': 1, 'b': 2}; void main() { late YAJsonIsolate isolate; - group('Initialize isolate manually', () { + + group('with manual initialize', () { setUp(() async { isolate = YAJsonIsolate(); await isolate.initialize(); @@ -16,18 +19,94 @@ void main() { await isolate.dispose(); }); - test('decode', () async { - final json = await isolate.decode(_jsonString); - expect(json, _jsonMap); + test('decodes a JSON object', () async { + expect(await isolate.decode(_jsonString), _jsonMap); + }); + + test('encodes a JSON object', () async { + expect(await isolate.encode(_jsonMap), _jsonString); + }); + }); + + group('without manual initialize', () { + setUp(() { + isolate = YAJsonIsolate(); + }); + + tearDown(() async { + await isolate.dispose(); + }); + + test('decodes lazily on first call', () async { + expect(await isolate.decode(_jsonString), _jsonMap); + }); + + test('encodes lazily on first call', () async { + expect(await isolate.encode(_jsonMap), _jsonString); + }); + }); + + group('data types', () { + setUp(() { + isolate = YAJsonIsolate(); + }); + + tearDown(() async { + await isolate.dispose(); + }); + + test('decodes a top level list', () async { + expect(await isolate.decode('[1,2,3]'), [1, 2, 3]); + }); + + test('encodes a top level list', () async { + expect(await isolate.encode([1, 2, 3]), '[1,2,3]'); + }); + + test('handles nested structures', () async { + const nested = { + 'list': [ + 1, + {'nested': true}, + ], + 'map': {'inner': null}, + }; + final encoded = await isolate.encode(nested); + expect(await isolate.decode(encoded), nested); }); - test('encode', () async { - final str = await isolate.encode(_jsonMap); - expect(str, _jsonString); + test('handles ints, doubles, booleans, null and strings', () async { + const value = { + 'int': 1, + 'double': 1.5, + 'boolTrue': true, + 'boolFalse': false, + 'null': null, + 'string': 'hello', + }; + final encoded = await isolate.encode(value); + expect(await isolate.decode(encoded), value); + }); + + test('preserves unicode characters', () async { + const value = {'emoji': '🚀', 'text': 'Grüße'}; + final encoded = await isolate.encode(value); + expect(await isolate.decode(encoded), value); + }); + + test('decodes a bare JSON primitive', () async { + expect(await isolate.decode('42'), 42); + expect(await isolate.decode('"text"'), 'text'); + expect(await isolate.decode('null'), isNull); + }); + + test('round trips an encoded value back to the original', () async { + final encoded = await isolate.encode(_jsonMap); + expect(await isolate.decode(encoded), _jsonMap); }); }); - group('Do not initialize isolate manually ', () { + group('error handling', () { setUp(() { isolate = YAJsonIsolate(); }); @@ -36,14 +115,100 @@ void main() { await isolate.dispose(); }); - test('decode', () async { - final json = await isolate.decode(_jsonString); - expect(json, _jsonMap); + test('throws FormatException for invalid JSON', () async { + await expectLater( + isolate.decode('{not valid json'), + throwsFormatException, + ); + }); + + test('throws for a non encodable object', () async { + await expectLater( + isolate.encode(DateTime.now()), + throwsA(isA()), + ); + }); + + test('stays usable after a decode error', () async { + await expectLater(isolate.decode('{bad'), throwsFormatException); + expect(await isolate.decode(_jsonString), _jsonMap); + }); + + test('stays usable after an encode error', () async { + await expectLater( + isolate.encode(DateTime.now()), + throwsA(isA()), + ); + expect(await isolate.encode(_jsonMap), _jsonString); + }); + }); + + group('concurrency and ordering', () { + setUp(() async { + isolate = YAJsonIsolate(); + await isolate.initialize(); + }); + + tearDown(() async { + await isolate.dispose(); + }); + + test('resolves concurrent decodes to the correct results', () async { + final results = await Future.wait([ + isolate.decode('1'), + isolate.decode('2'), + isolate.decode('3'), + isolate.decode('[4]'), + ]); + expect(results, [ + 1, + 2, + 3, + [4], + ]); }); - test('encode', () async { - final str = await isolate.encode(_jsonMap); - expect(str, _jsonString); + test('resolves interleaved encodes and decodes', () async { + final results = await Future.wait([ + isolate.encode({'a': 1}), + isolate.decode('[1,2]'), + isolate.encode([true, false]), + ]); + expect(results, [ + '{"a":1}', + [1, 2], + '[true,false]', + ]); + }); + + test('handles many sequential operations', () async { + for (var i = 0; i < 50; i++) { + expect(await isolate.decode('$i'), i); + } + }); + }); + + group('lifecycle', () { + test('dispose can be awaited on a lazily initialized isolate', () async { + final isolate = YAJsonIsolate(); + await isolate.decode(_jsonString); + await expectLater(isolate.dispose(), completes); + }); + + test('two isolates operate independently', () async { + final first = YAJsonIsolate(); + final second = YAJsonIsolate(); + addTearDown(first.dispose); + addTearDown(second.dispose); + + final results = await Future.wait([ + first.decode('[1]'), + second.decode('[2]'), + ]); + expect(results, [ + [1], + [2], + ]); }); }); } From 10f4e196b87185bc1e42ef6bb1bc104a49012dde Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Fri, 10 Jul 2026 16:15:08 +0200 Subject: [PATCH 2/2] test(yet_another_json_isolate): avoid shadowing outer isolate variable --- .../test/yet_another_json_isolate_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yet_another_json_isolate/test/yet_another_json_isolate_test.dart b/packages/yet_another_json_isolate/test/yet_another_json_isolate_test.dart index b741f1b36..d0945c541 100644 --- a/packages/yet_another_json_isolate/test/yet_another_json_isolate_test.dart +++ b/packages/yet_another_json_isolate/test/yet_another_json_isolate_test.dart @@ -190,9 +190,9 @@ void main() { group('lifecycle', () { test('dispose can be awaited on a lazily initialized isolate', () async { - final isolate = YAJsonIsolate(); - await isolate.decode(_jsonString); - await expectLater(isolate.dispose(), completes); + final lazyIsolate = YAJsonIsolate(); + await lazyIsolate.decode(_jsonString); + await expectLater(lazyIsolate.dispose(), completes); }); test('two isolates operate independently', () async {