@@ -3,6 +3,7 @@ import 'dart:convert';
33
44import 'package:checks/checks.dart' ;
55import 'package:file_picker/file_picker.dart' ;
6+ import 'package:flutter/services.dart' ;
67import 'package:flutter_checks/flutter_checks.dart' ;
78import 'package:http/http.dart' as http;
89import 'package:flutter/material.dart' ;
@@ -575,6 +576,106 @@ void main() {
575576
576577 // TODO test what happens when capturing/uploading fails
577578 });
579+
580+ group ('attach from keyboard' , () {
581+ // This is adapted from:
582+ // https://github.com/flutter/flutter/blob/0ffc4ce00ea7bb912e379adf39354644eab2c17e/packages/flutter/test/widgets/editable_text_test.dart#L724-L740
583+ Future <void > insertContentFromKeyboard (WidgetTester tester, {
584+ required List <int >? data,
585+ required String attachedFileUrl,
586+ required String mimeType,
587+ }) async {
588+ await tester.showKeyboard (contentInputFinder);
589+ // This invokes [EditableText.performAction] on the content [TextField],
590+ // which did not expose an API for testing.
591+ // TODO(upstream): support a better API for testing this
592+ await tester.binding.defaultBinaryMessenger.handlePlatformMessage (
593+ SystemChannels .textInput.name,
594+ SystemChannels .textInput.codec.encodeMethodCall (
595+ MethodCall ('TextInputClient.performAction' , < dynamic > [
596+ - 1 ,
597+ 'TextInputAction.commitContent' ,
598+ // This fakes data originally provided by the Flutter engine:
599+ // https://github.com/flutter/flutter/blob/0ffc4ce00ea7bb912e379adf39354644eab2c17e/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java#L497-L548
600+ {
601+ "mimeType" : mimeType,
602+ "data" : data,
603+ "uri" : attachedFileUrl,
604+ },
605+ ])),
606+ (ByteData ? data) {});
607+ }
608+
609+ testWidgets ('success' , (tester) async {
610+ const fileContent = [1 , 0 , 1 , 0 , 0 ];
611+ await prepare (tester);
612+ const uploadUrl = '/user_uploads/1/4e/m2A3MSqFnWRLUf9SaPzQ0Up_/test.gif' ;
613+ connection.prepare (json: UploadFileResult (uri: uploadUrl).toJson ());
614+ await insertContentFromKeyboard (tester,
615+ data: fileContent,
616+ attachedFileUrl:
617+ 'content://com.zulip.android.zulipboard.provider'
618+ '/root/com.zulip.android.zulipboard/candidate_temp/test.gif' ,
619+ mimeType: 'image/gif' );
620+
621+ await tester.pump ();
622+ check (controller! .content.text)
623+ .equals ('see image: [Uploading test.gif…]()\n\n ' );
624+ // (the request is checked more thoroughly in API tests)
625+ check (connection.lastRequest! ).isA< http.MultipartRequest > ()
626+ ..method.equals ('POST' )
627+ ..files.single.which ((it) => it
628+ ..field.equals ('file' )
629+ ..length.equals (fileContent.length)
630+ ..filename.equals ('test.gif' )
631+ ..contentType.asString.equals ('image/gif' )
632+ ..has <Future <List <int >>>((f) => f.finalize ().toBytes (), 'contents' )
633+ .completes ((it) => it.deepEquals (fileContent))
634+ );
635+ checkAppearsLoading (tester, true );
636+
637+ await tester.pump (Duration .zero);
638+ check (controller! .content.text)
639+ .equals ('see image: [test.gif]($uploadUrl )\n\n ' );
640+ checkAppearsLoading (tester, false );
641+ });
642+
643+ testWidgets ('data is null' , (tester) async {
644+ await prepare (tester);
645+ await insertContentFromKeyboard (tester,
646+ data: null ,
647+ attachedFileUrl:
648+ 'content://com.zulip.android.zulipboard.provider'
649+ '/root/com.zulip.android.zulipboard/candidate_temp/test.gif' ,
650+ mimeType: 'image/jpeg' );
651+
652+ await tester.pump ();
653+ check (controller! .content.text).equals ('see image: ' );
654+ check (connection.takeRequests ()).isEmpty ();
655+ checkErrorDialog (tester,
656+ expectedTitle: 'Content not inserted' ,
657+ expectedMessage: 'The file to be inserted is empty or cannot be accessed.' );
658+ checkAppearsLoading (tester, false );
659+ });
660+
661+ testWidgets ('data is empty' , (tester) async {
662+ await prepare (tester);
663+ await insertContentFromKeyboard (tester,
664+ data: [],
665+ attachedFileUrl:
666+ 'content://com.zulip.android.zulipboard.provider'
667+ '/root/com.zulip.android.zulipboard/candidate_temp/test.gif' ,
668+ mimeType: 'image/jpeg' );
669+
670+ await tester.pump ();
671+ check (controller! .content.text).equals ('see image: ' );
672+ check (connection.takeRequests ()).isEmpty ();
673+ checkErrorDialog (tester,
674+ expectedTitle: 'Content not inserted' ,
675+ expectedMessage: 'The file to be inserted is empty or cannot be accessed.' );
676+ checkAppearsLoading (tester, false );
677+ });
678+ });
578679 });
579680
580681 group ('error banner' , () {
0 commit comments