@@ -3,6 +3,7 @@ import 'dart:convert';
3
3
4
4
import 'package:checks/checks.dart' ;
5
5
import 'package:file_picker/file_picker.dart' ;
6
+ import 'package:flutter/services.dart' ;
6
7
import 'package:flutter_checks/flutter_checks.dart' ;
7
8
import 'package:http/http.dart' as http;
8
9
import 'package:flutter/material.dart' ;
@@ -575,6 +576,91 @@ void main() {
575
576
576
577
// TODO test what happens when capturing/uploading fails
577
578
});
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
+ // This fakes data originally provided by the Flutter engine:
589
+ // https://github.com/flutter/flutter/blob/0ffc4ce00ea7bb912e379adf39354644eab2c17e/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java#L497-L548
590
+ final ByteData ? messageBytes = const JSONMessageCodec ().encodeMessage ({
591
+ 'args' : [
592
+ - 1 ,
593
+ 'TextInputAction.commitContent' ,
594
+ {
595
+ "mimeType" : mimeType,
596
+ "data" : data,
597
+ "uri" : attachedFileUrl,
598
+ },
599
+ ],
600
+ 'method' : 'TextInputClient.performAction' ,
601
+ });
602
+ // This calls [EditableText]'s implementation of
603
+ // [TextInputClient.performAction] on the content [TextField],
604
+ // which did not expose an API for testing.
605
+ await tester.binding.defaultBinaryMessenger.handlePlatformMessage (
606
+ 'flutter/textinput' ,
607
+ messageBytes,
608
+ (ByteData ? _) {},
609
+ );
610
+ }
611
+
612
+ testWidgets ('success' , (tester) async {
613
+ const fileContent = [1 , 0 , 1 , 0 , 0 ];
614
+ await prepare (tester);
615
+ const uploadUrl = '/user_uploads/1/4e/m2A3MSqFnWRLUf9SaPzQ0Up_/test.gif' ;
616
+ connection.prepare (json: UploadFileResult (uri: uploadUrl).toJson ());
617
+ await insertContentFromKeyboard (tester,
618
+ data: fileContent,
619
+ attachedFileUrl:
620
+ 'content://com.samsung.android.zulipboard.provider'
621
+ '/root/com.zulip.android.zulipboard/candidate_temp/test.gif' ,
622
+ mimeType: 'image/gif' );
623
+
624
+ await tester.pump ();
625
+ check (controller! .content.text)
626
+ .equals ('see image: [Uploading test.gif…]()\n\n ' );
627
+ check (connection.lastRequest! ).isA< http.MultipartRequest > ()
628
+ ..method.equals ('POST' )
629
+ ..files.single.which ((it) => it
630
+ ..field.equals ('file' )
631
+ ..length.equals (fileContent.length)
632
+ ..filename.equals ('test.gif' )
633
+ ..contentType.asString.equals ('image/gif' )
634
+ ..has <Future <List <int >>>((f) => f.finalize ().toBytes (), 'contents' )
635
+ .completes ((it) => it.deepEquals (fileContent))
636
+ );
637
+ checkAppearsLoading (tester, true );
638
+
639
+ await tester.pump (Duration .zero);
640
+ check (controller! .content.text)
641
+ .equals ('see image: [test.gif]($uploadUrl )\n\n ' );
642
+ checkAppearsLoading (tester, false );
643
+ });
644
+
645
+ testWidgets ('empty file' , (tester) async {
646
+ await prepare (tester);
647
+ await insertContentFromKeyboard (tester,
648
+ data: [],
649
+ attachedFileUrl:
650
+ 'content://com.samsung.android.zulipboard.provider'
651
+ '/root/com.zulip.android.zulipboard/candidate_temp/test.gif' ,
652
+ mimeType: 'image/jpeg' );
653
+
654
+ await tester.pump ();
655
+ check (controller! .content.text).equals ('see image: ' );
656
+ check (connection.takeRequests ()).isEmpty ();
657
+ checkErrorDialog (tester,
658
+ expectedTitle: 'Content not inserted' ,
659
+ expectedMessage: 'The file to be inserted is empty or cannot be found.' );
660
+ checkAppearsLoading (tester, false );
661
+ });
662
+
663
+ });
578
664
});
579
665
580
666
group ('error banner' , () {
0 commit comments