// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This files contains message codec tests that are supported both on the Web // and in the VM. For VM-only tests see message_codecs_vm_test.dart. import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/foundation.dart' show WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'message_codecs_testing.dart'; void main() { group('Binary codec', () { const MessageCodec binary = BinaryCodec(); test('should encode and decode simple messages', () { checkEncodeDecode(binary, null); checkEncodeDecode(binary, ByteData(0)); checkEncodeDecode(binary, ByteData(4)..setInt32(0, -7)); }); }); group('String codec', () { const MessageCodec string = StringCodec(); test('should encode and decode simple messages', () { checkEncodeDecode(string, null); checkEncodeDecode(string, ''); checkEncodeDecode(string, 'hello'); checkEncodeDecode(string, 'special chars >\u263A\u{1F602}<'); }); test('ByteData with offset', () { const MessageCodec string = StringCodec(); final ByteData helloWorldByteData = string.encodeMessage('hello world')!; final ByteData helloByteData = string.encodeMessage('hello')!; final ByteData offsetByteData = ByteData.view( helloWorldByteData.buffer, helloByteData.lengthInBytes, helloWorldByteData.lengthInBytes - helloByteData.lengthInBytes, ); expect(string.decodeMessage(offsetByteData), ' world'); }); }); group('Standard method codec', () { const MethodCodec method = StandardMethodCodec(); const StandardMessageCodec messageCodec = StandardMessageCodec(); test('Should encode and decode objects produced from codec', () { final ByteData? data = messageCodec.encodeMessage({ 'foo': true, 3: 'fizz', }); expect(messageCodec.decodeMessage(data), { 'foo': true, 3: 'fizz', }); }); test('should decode error envelope without native stacktrace', () { final ByteData errorData = method.encodeErrorEnvelope( code: 'errorCode', message: 'errorMessage', details: 'errorDetails', ); expect( () => method.decodeEnvelope(errorData), throwsA(predicate( (PlatformException e) => e.code == 'errorCode' && e.message == 'errorMessage' && e.details == 'errorDetails', )), ); }); test('should decode error envelope with native stacktrace.', () { final WriteBuffer buffer = WriteBuffer(); buffer.putUint8(1); messageCodec.writeValue(buffer, 'errorCode'); messageCodec.writeValue(buffer, 'errorMessage'); messageCodec.writeValue(buffer, 'errorDetails'); messageCodec.writeValue(buffer, 'errorStacktrace'); final ByteData errorData = buffer.done(); expect( () => method.decodeEnvelope(errorData), throwsA(predicate((PlatformException e) => e.stacktrace == 'errorStacktrace')), ); }); test('should allow null error message,', () { final ByteData errorData = method.encodeErrorEnvelope( code: 'errorCode', details: 'errorDetails', ); expect( () => method.decodeEnvelope(errorData), throwsA( predicate((PlatformException e) { return e.code == 'errorCode' && e.message == null && e.details == 'errorDetails'; }), ), ); }); }); group('Json method codec', () { const JsonCodec json = JsonCodec(); const StringCodec stringCodec = StringCodec(); const JSONMethodCodec jsonMethodCodec = JSONMethodCodec(); test('should decode error envelope without native stacktrace', () { final ByteData errorData = jsonMethodCodec.encodeErrorEnvelope( code: 'errorCode', message: 'errorMessage', details: 'errorDetails', ); expect( () => jsonMethodCodec.decodeEnvelope(errorData), throwsA(predicate( (PlatformException e) => e.code == 'errorCode' && e.message == 'errorMessage' && e.details == 'errorDetails', )), ); }); test('should decode error envelope with native stacktrace.', () { final ByteData? errorData = stringCodec.encodeMessage(json.encode([ 'errorCode', 'errorMessage', 'errorDetails', 'errorStacktrace', ])); expect( () => jsonMethodCodec.decodeEnvelope(errorData!), throwsA(predicate((PlatformException e) => e.stacktrace == 'errorStacktrace')), ); }); }); group('JSON message codec', () { const MessageCodec json = JSONMessageCodec(); test('should encode and decode simple messages', () { checkEncodeDecode(json, null); checkEncodeDecode(json, true); checkEncodeDecode(json, false); checkEncodeDecode(json, 7); checkEncodeDecode(json, -7); checkEncodeDecode(json, 98742923489); checkEncodeDecode(json, -98742923489); checkEncodeDecode(json, 3.14); checkEncodeDecode(json, ''); checkEncodeDecode(json, 'hello'); checkEncodeDecode(json, 'special chars >\u263A\u{1F602}<'); }); test('should encode and decode composite message', () { final List message = [ null, true, false, -707, -7000000007, -3.14, '', 'hello', ['nested', []], {'a': 'nested', 'b': {}}, 'world', ]; checkEncodeDecode(json, message); }); }); group('Standard message codec', () { const MessageCodec standard = StandardMessageCodec(); test('should encode sizes correctly at boundary cases', () { checkEncoding( standard, Uint8List(253), [8, 253, ...List.filled(253, 0)], ); checkEncoding( standard, Uint8List(254), [8, 254, 254, 0, ...List.filled(254, 0)], ); checkEncoding( standard, Uint8List(0xffff), [8, 254, 0xff, 0xff, ...List.filled(0xffff, 0)], ); checkEncoding( standard, Uint8List(0xffff + 1), [8, 255, 0, 0, 1, 0, ...List.filled(0xffff + 1, 0)], ); }); test('should encode and decode simple messages', () { checkEncodeDecode(standard, null); checkEncodeDecode(standard, true); checkEncodeDecode(standard, false); checkEncodeDecode(standard, 7); checkEncodeDecode(standard, -7); checkEncodeDecode(standard, 98742923489); checkEncodeDecode(standard, -98742923489); checkEncodeDecode(standard, 3.14); checkEncodeDecode(standard, double.infinity); checkEncodeDecode(standard, double.nan); checkEncodeDecode(standard, ''); checkEncodeDecode(standard, 'hello'); checkEncodeDecode(standard, 'special chars >\u263A\u{1F602}<'); }); test('should encode and decode composite message', () { final List message = [ null, true, false, -707, -7000000007, -3.14, '', 'hello', Uint8List.fromList([0xBA, 0x5E, 0xBA, 0x11]), Int32List.fromList([-0x7fffffff - 1, 0, 0x7fffffff]), null, // ensures the offset of the following list is unaligned. null, // ensures the offset of the following list is unaligned. Float64List.fromList([ double.negativeInfinity, -double.maxFinite, -double.minPositive, -0.0, 0.0, double.minPositive, double.maxFinite, double.infinity, double.nan, ]), Float32List.fromList([ double.negativeInfinity, -double.maxFinite, -double.minPositive, -0.0, 0.0, double.minPositive, double.maxFinite, double.infinity, double.nan, ]), ['nested', []], {'a': 'nested', null: {}}, 'world', ]; checkEncodeDecode(standard, message); }); test('should align doubles to 8 bytes', () { checkEncoding( standard, 1.0, [ 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xf0, 0x3f, ], ); }); }); test('toString works as intended', () async { const MethodCall methodCall = MethodCall('sample method'); final PlatformException platformException = PlatformException(code: '100'); final MissingPluginException missingPluginException = MissingPluginException(); expect(methodCall.toString(), 'MethodCall(sample method, null)'); expect(platformException.toString(), 'PlatformException(100, null, null, null)'); expect(missingPluginException.toString(), 'MissingPluginException(null)'); }); }