FlutterのWindows実行版を作る際に、ファイルのDrag&Dropをdesktop_dropで実装したのだけど、それを使ったウィジェットテストがちょっと独自だったので、調べた結果を書き記す。

今回作ったウィジェット
TextFiledをDropTarget(desktop_dropで提供されているファイルのDropに対応したウィジェット)でラッピングして、ファイルエクスプローラーからファイルをドロップしてファイル名の入力の代替にできないかと思って作ったもの。
動きとしてはこんな感じ。

ファイル選択ダイアログを使うより、もうちょっと簡単にファイルを指定できないかなと思って作った。
Dropされたファイル名はTextFieldのonSubmittedと同様な形でコールバック側に渡すようにしている。
テスト方法
参考になるのはdesktop_dropのgithub内の以下の2つのソース。
一つは、テストルーチン。
もう一つはチャネル側。
テストルーチン側に定義されている_invokePlatformMethodでメッセージを送り込むことでDropイベントを処理させるという感じ。
実際どういったメッセージを送り込むのかについては、チャネル側のソースが参考になる。
主に使うメッセージは、’entered’:これはDropカーソルがウィジェット内に入ったとき、’exited’:これはDropカーソルがウィジェット外に出たとき、’performOperation’:Dropをしたとき、の3種類。
ただLinux、MacOSでは別のメッセージが用意されているので、全機種での実行を評価するためには、’performOperation’だけではなく、それらメッセージのテストもする必要があると思う。
注意点は’performOperation’。これは事前に’entered’を送っておきウィジェット上にDropカーソルがある状態になっていないとイベントが発生しない。
実装例
DropableTextFieldが今回テストしたいTextFieldをDropTargetでラッピングしたウィジェット名。
Drop enter時に色の変更をしてるのでそのテスト評価、ファイルドロップ時にそのファイル(文字列)が渡ってきているかどうかのチェックを行っている。
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late TextEditingController controller;
setUp(() {
controller = TextEditingController();
});
group('drop test', () {
setUp(() {
DesktopDrop.instance.init();
});
testWidgets('enter/exit color', (WidgetTester tester) async {
final top = MaterialApp(
home: Scaffold(
body: DropableTextField(
controller: controller,
)));
await tester.pumpWidget(top);
await _invokePlatformMethod(const MethodCall('entered', [1.0, 2.0]));
await tester.pump(); // 内部でウィジェットの更新をしているので必要
// Verify that our counter starts at 0.
expect(
(tester.firstWidget(find.byType(TextField)) as TextField)
.decoration!
.fillColor,
Colors.blue.withValues(alpha: 0.4));
expect(
(tester.firstWidget(find.byType(TextField)) as TextField)
.decoration!
.filled,
isTrue);
await _invokePlatformMethod(const MethodCall('exited', [1.0, 2.0]));
await tester.pump();
expect(
(tester.firstWidget(find.byType(TextField)) as TextField)
.decoration!
.fillColor,
isNull);
expect(
(tester.firstWidget(find.byType(TextField)) as TextField)
.decoration!
.filled,
isFalse);
});
testWidgets('On drop', (WidgetTester tester) async {
String onChanged = "";
String onSubmitted = "";
final top = MaterialApp(
home: Scaffold(
body: DropableTextField(
controller: controller,
onChanged: (val) => onChanged = val,
onSubmitted: (val) => onSubmitted = val,
)));
await tester.pumpWidget(top);
await _invokePlatformMethod(const MethodCall('entered', [1.0, 2.0]));
// ドロップファイルを2つ渡して1つ目に反応するかのテストも行う
// pumpアクションは内部でウィジェットの更新をしないので必要ない
await _invokePlatformMethod(
const MethodCall('performOperation', ['file.txt', 'file2.txt']));
expect(onChanged.isEmpty, isTrue);
expect(onSubmitted, "file.txt");
});
)
コメント