From 84acd910374cd2bc842658dce138570e40664c1e Mon Sep 17 00:00:00 2001 From: Seungsoo Lee Date: Wed, 17 Jun 2026 21:20:37 +0900 Subject: [PATCH 1/2] [keyboard_detection] Add regression integration tests Expand the integration test suite for the keyboard detection controller from 2 to 20 cases, driving the input-panel event channel with simulated platform messages so the tests are deterministic and need no real keyboard: - Initial unknown state and unknown fallback for unrecognized events. - All keyboard states: visibling, visible, hiding, hidden. - stateAsBool mapping, including the transitional states with and without includeTransitionalState. - Keyboard metrics (width, height/size, position) updates on show with dimensions, reset on hide, and isSizeLoaded / ensureSizeLoaded. - Robustness against malformed events (non-map / missing or wrong-typed state). - Notifications via the state stream and the onChanged callback. - Registered callbacks: delivery, self-unregister on returning false, unregisterCallback and unregisterAllCallbacks. - No further state changes are reported after dispose. This is a Tizen-exclusive plugin with no upstream package. Test-only change, recorded under a NEXT changelog entry without a version bump. Validated on a Raspberry Pi 4 (Tizen) device: 20 passed, 0 failed. --- packages/keyboard_detection/CHANGELOG.md | 4 + .../keyboard_detection_tizen_test.dart | 334 +++++++++++++++++- 2 files changed, 319 insertions(+), 19 deletions(-) diff --git a/packages/keyboard_detection/CHANGELOG.md b/packages/keyboard_detection/CHANGELOG.md index 607323422..5d34a1ebf 100644 --- a/packages/keyboard_detection/CHANGELOG.md +++ b/packages/keyboard_detection/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Add 18 integration test cases. + ## 0.1.0 * Initial release. diff --git a/packages/keyboard_detection/example/integration_test/keyboard_detection_tizen_test.dart b/packages/keyboard_detection/example/integration_test/keyboard_detection_tizen_test.dart index 4a8ba1164..64e9b5f38 100644 --- a/packages/keyboard_detection/example/integration_test/keyboard_detection_tizen_test.dart +++ b/packages/keyboard_detection/example/integration_test/keyboard_detection_tizen_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -13,7 +15,9 @@ void main() { const String channelName = 'tizen/internal/inputpanel'; const StandardMethodCodec codec = StandardMethodCodec(); - Future emit(WidgetTester tester, Map payload) async { + // Injects an event into the input-panel [EventChannel] the controller + // listens to, simulating a message coming from the flutter-tizen embedder. + Future emit(WidgetTester tester, Object? payload) async { final ByteData data = codec.encodeSuccessEnvelope(payload); await tester.binding.defaultBinaryMessenger.handlePlatformMessage( channelName, @@ -22,25 +26,317 @@ void main() { ); } - testWidgets('reports visible on show event', (WidgetTester tester) async { - final KeyboardDetectionController controller = - KeyboardDetectionController(); - await emit(tester, {'state': 'show'}); - await tester.pump(); - expect(controller.state, KeyboardState.visible); - expect(controller.stateAsBool(), isTrue); - await controller.dispose(); + group('state reporting', () { + testWidgets('starts in the unknown state', (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + expect(controller.state, KeyboardState.unknown); + expect(controller.stateAsBool(), isNull); + await controller.dispose(); + }); + + testWidgets('reports visibling on will_show event', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'will_show'}); + await tester.pump(); + expect(controller.state, KeyboardState.visibling); + await controller.dispose(); + }); + + testWidgets('reports visible on show event', (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'show'}); + await tester.pump(); + expect(controller.state, KeyboardState.visible); + expect(controller.stateAsBool(), isTrue); + await controller.dispose(); + }); + + testWidgets('reports hiding on will_hide event', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'show'}); + await tester.pump(); + await emit(tester, {'state': 'will_hide'}); + await tester.pump(); + expect(controller.state, KeyboardState.hiding); + await controller.dispose(); + }); + + testWidgets('reports hidden on hide event', (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'show'}); + await tester.pump(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(controller.state, KeyboardState.hidden); + expect(controller.stateAsBool(), isFalse); + await controller.dispose(); + }); + + testWidgets('falls back to unknown for an unrecognized event', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'show'}); + await tester.pump(); + await emit(tester, {'state': 'something_else'}); + await tester.pump(); + expect(controller.state, KeyboardState.unknown); + await controller.dispose(); + }); + + testWidgets('ignores events without a valid state field', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + // Reach a known state first, so the assertions verify that the invalid + // events are ignored (state preserved) rather than merely matching the + // initial unknown state. + await emit(tester, {'state': 'show'}); + await tester.pump(); + expect(controller.state, KeyboardState.visible); + await emit(tester, {'noState': true}); + await tester.pump(); + expect(controller.state, KeyboardState.visible); + await emit(tester, {'state': 123}); + await tester.pump(); + expect(controller.state, KeyboardState.visible); + await controller.dispose(); + }); + }); + + group('stateAsBool', () { + testWidgets('maps the transitional visibling state', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'will_show'}); + await tester.pump(); + expect(controller.stateAsBool(), isFalse); + expect(controller.stateAsBool(true), isTrue); + await controller.dispose(); + }); + + testWidgets('maps the transitional hiding state', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'will_hide'}); + await tester.pump(); + expect(controller.stateAsBool(), isTrue); + expect(controller.stateAsBool(true), isFalse); + await controller.dispose(); + }); + }); + + group('keyboard metrics', () { + testWidgets('size, width and position are zero before any event', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + expect(controller.size, 0); + expect(controller.width, 0); + expect(controller.position, Offset.zero); + expect(controller.isSizeLoaded, isFalse); + await controller.dispose(); + }); + + testWidgets('updates metrics from a show event carrying dimensions', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, { + 'state': 'show', + 'width': 1080.0, + 'height': 420.0, + 'x': 0.0, + 'y': 1500.0, + }); + await tester.pump(); + expect(controller.width, 1080.0); + expect(controller.size, 420.0); + expect(controller.position, const Offset(0, 1500)); + expect(controller.isSizeLoaded, isTrue); + await controller.dispose(); + }); + + testWidgets('ensureSizeLoaded completes once metrics arrive', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + final Future sizeLoaded = controller.ensureSizeLoaded; + await emit(tester, { + 'state': 'show', + 'width': 1080.0, + 'height': 420.0, + }); + await tester.pump(); + await expectLater(sizeLoaded, completes); + expect(controller.isSizeLoaded, isTrue); + await controller.dispose(); + }); + + testWidgets('resets metrics to zero on hide', (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, { + 'state': 'show', + 'width': 1080.0, + 'height': 420.0, + 'x': 0.0, + 'y': 1500.0, + }); + await tester.pump(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(controller.width, 0); + expect(controller.size, 0); + expect(controller.position, Offset.zero); + await controller.dispose(); + }); + }); + + group('notifications', () { + testWidgets('stream emits state changes in order', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + final List seen = []; + final StreamSubscription subscription = + controller.stream.listen(seen.add); + await emit(tester, {'state': 'will_show'}); + await tester.pump(); + await emit(tester, {'state': 'show'}); + await tester.pump(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(seen, [ + KeyboardState.visibling, + KeyboardState.visible, + KeyboardState.hidden, + ]); + await subscription.cancel(); + await controller.dispose(); + }); + + testWidgets('onChanged is invoked on every state change', + (WidgetTester tester) async { + final List seen = []; + final KeyboardDetectionController controller = + KeyboardDetectionController(onChanged: seen.add); + await emit(tester, {'state': 'show'}); + await tester.pump(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(seen, [ + KeyboardState.visible, + KeyboardState.hidden, + ]); + await controller.dispose(); + }); + }); + + group('registered callbacks', () { + testWidgets('a registered callback receives state changes', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + final List seen = []; + controller.registerCallback((KeyboardState state) { + seen.add(state); + return true; + }); + await emit(tester, {'state': 'show'}); + await tester.pump(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(seen, [ + KeyboardState.visible, + KeyboardState.hidden, + ]); + await controller.dispose(); + }); + + testWidgets('a callback returning false unregisters itself', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + int calls = 0; + controller.registerCallback((KeyboardState state) { + calls++; + return false; + }); + await emit(tester, {'state': 'show'}); + await tester.pump(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(calls, 1); + await controller.dispose(); + }); + + testWidgets('unregisterCallback stops further invocations', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + final List seen = []; + bool callback(KeyboardState state) { + seen.add(state); + return true; + } + + controller.registerCallback(callback); + await emit(tester, {'state': 'show'}); + await tester.pump(); + controller.unregisterCallback(callback); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(seen, [KeyboardState.visible]); + await controller.dispose(); + }); + + testWidgets('unregisterAllCallbacks removes every callback', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + int first = 0; + int second = 0; + controller.registerCallback((KeyboardState state) { + first++; + return true; + }); + controller.registerCallback((KeyboardState state) { + second++; + return true; + }); + await emit(tester, {'state': 'show'}); + await tester.pump(); + controller.unregisterAllCallbacks(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(first, 1); + expect(second, 1); + await controller.dispose(); + }); }); - testWidgets('reports hidden on hide event', (WidgetTester tester) async { - final KeyboardDetectionController controller = - KeyboardDetectionController(); - await emit(tester, {'state': 'show'}); - await tester.pump(); - await emit(tester, {'state': 'hide'}); - await tester.pump(); - expect(controller.state, KeyboardState.hidden); - expect(controller.stateAsBool(), isFalse); - await controller.dispose(); + group('lifecycle', () { + testWidgets('does not report state changes after dispose', + (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'show'}); + await tester.pump(); + expect(controller.state, KeyboardState.visible); + await controller.dispose(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(controller.state, KeyboardState.visible); + }); }); } From 7fd41e299dc52316bf22f8726c7373ea6eeb861a Mon Sep 17 00:00:00 2001 From: Seungsoo Lee Date: Mon, 22 Jun 2026 13:20:02 +0900 Subject: [PATCH 2/2] [keyboard_detection] Consolidate stateAsBool checks into state reporting tests Merge the stateAsBool group into the existing will_show and will_hide state reporting tests, which already cover the same event setup. Co-Authored-By: Claude Sonnet 4.6 --- .../keyboard_detection_tizen_test.dart | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/packages/keyboard_detection/example/integration_test/keyboard_detection_tizen_test.dart b/packages/keyboard_detection/example/integration_test/keyboard_detection_tizen_test.dart index 64e9b5f38..5f161e459 100644 --- a/packages/keyboard_detection/example/integration_test/keyboard_detection_tizen_test.dart +++ b/packages/keyboard_detection/example/integration_test/keyboard_detection_tizen_test.dart @@ -42,6 +42,8 @@ void main() { await emit(tester, {'state': 'will_show'}); await tester.pump(); expect(controller.state, KeyboardState.visibling); + expect(controller.stateAsBool(), isFalse); + expect(controller.stateAsBool(true), isTrue); await controller.dispose(); }); @@ -64,6 +66,8 @@ void main() { await emit(tester, {'state': 'will_hide'}); await tester.pump(); expect(controller.state, KeyboardState.hiding); + expect(controller.stateAsBool(), isTrue); + expect(controller.stateAsBool(true), isFalse); await controller.dispose(); }); @@ -111,30 +115,6 @@ void main() { }); }); - group('stateAsBool', () { - testWidgets('maps the transitional visibling state', - (WidgetTester tester) async { - final KeyboardDetectionController controller = - KeyboardDetectionController(); - await emit(tester, {'state': 'will_show'}); - await tester.pump(); - expect(controller.stateAsBool(), isFalse); - expect(controller.stateAsBool(true), isTrue); - await controller.dispose(); - }); - - testWidgets('maps the transitional hiding state', - (WidgetTester tester) async { - final KeyboardDetectionController controller = - KeyboardDetectionController(); - await emit(tester, {'state': 'will_hide'}); - await tester.pump(); - expect(controller.stateAsBool(), isTrue); - expect(controller.stateAsBool(true), isFalse); - await controller.dispose(); - }); - }); - group('keyboard metrics', () { testWidgets('size, width and position are zero before any event', (WidgetTester tester) async {