diff --git a/README.md b/README.md index 127ef78..3ce05ef 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,24 @@ void main() { }); } ``` + +## Simulate popular mobile devices +`flutter_test_robots` can configure a `WidgetTester` viewport like popular recent phones. + +```dart +void main() { + testWidgets("renders on iPhone 16 Pro Max", (tester) async { + tester.asIPhone16ProMax(); + + await tester.pumpWidget(MyApp()); + }); + + testWidgets("renders on every built-in test device", (tester) async { + for (final device in TestDevices.all) { + tester.configureForDevice(device); + + await tester.pumpWidget(MyApp()); + } + }); +} +``` diff --git a/lib/flutter_test_robots.dart b/lib/flutter_test_robots.dart index b8b3380..8787917 100644 --- a/lib/flutter_test_robots.dart +++ b/lib/flutter_test_robots.dart @@ -1,6 +1,7 @@ library flutter_test_robots; export 'src/clipboard.dart'; +export 'src/device_configurations.dart'; export 'src/input_method_engine.dart'; export 'src/keyboard.dart'; export 'src/scrollbar.dart'; diff --git a/lib/src/device_configurations.dart b/lib/src/device_configurations.dart new file mode 100644 index 0000000..af2930d --- /dev/null +++ b/lib/src/device_configurations.dart @@ -0,0 +1,232 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; + +/// Extensions for configuring a [WidgetTester] like a popular mobile device. +extension DeviceConfigurations on WidgetTester { + void asIPhone14() { + configureForDevice(TestDevices.iPhone14); + } + + void asIPhone15() { + configureForDevice(TestDevices.iPhone15); + } + + void asIPhone15Pro() { + configureForDevice(TestDevices.iPhone15Pro); + } + + void asIPhone15ProMax() { + configureForDevice(TestDevices.iPhone15ProMax); + } + + void asIPhone16() { + configureForDevice(TestDevices.iPhone16); + } + + void asIPhone16Pro() { + configureForDevice(TestDevices.iPhone16Pro); + } + + void asIPhone16ProMax() { + configureForDevice(TestDevices.iPhone16ProMax); + } + + void asIPhone16e() { + configureForDevice(TestDevices.iPhone16e); + } + + void asIPhone17() { + configureForDevice(TestDevices.iPhone17); + } + + void asIPhone17ProMax() { + configureForDevice(TestDevices.iPhone17ProMax); + } + + void asSamsungGalaxyA06() { + configureForDevice(TestDevices.samsungGalaxyA06); + } + + void asSamsungGalaxyA14() { + configureForDevice(TestDevices.samsungGalaxyA14); + } + + void asSamsungGalaxyA15() { + configureForDevice(TestDevices.samsungGalaxyA15); + } + + void asSamsungGalaxyA16() { + configureForDevice(TestDevices.samsungGalaxyA16); + } + + void asSamsungGalaxyS24Ultra() { + configureForDevice(TestDevices.samsungGalaxyS24Ultra); + } + + void asSamsungGalaxyS25Ultra() { + configureForDevice(TestDevices.samsungGalaxyS25Ultra); + } + + /// Configures the tester with [configuration]. + void configureForDevice(DeviceConfiguration configuration) { + view + ..physicalSize = configuration.physicalSize + ..devicePixelRatio = configuration.devicePixelRatio; + + addTearDown(resetDeviceConfiguration); + } + + /// Resets the tester's device viewport overrides. + void resetDeviceConfiguration() { + view + ..resetPhysicalSize() + ..resetDevicePixelRatio(); + } +} + +/// Popular mobile device configurations for widget tests. +/// +/// This list favors globally popular phone models released or sold heavily from +/// 2023 through 2025. The iPhone values use Apple's native render scale. The +/// Samsung values use practical Android density defaults for Flutter tests. +class TestDevices { + /// All built-in test device configurations. + static const List all = [ + iPhone14, + iPhone15, + iPhone15Pro, + iPhone15ProMax, + iPhone16, + iPhone16Pro, + iPhone16ProMax, + iPhone16e, + iPhone17, + iPhone17ProMax, + samsungGalaxyA06, + samsungGalaxyA14, + samsungGalaxyA15, + samsungGalaxyA16, + samsungGalaxyS24Ultra, + samsungGalaxyS25Ultra, + ]; + + static const DeviceConfiguration iPhone14 = DeviceConfiguration( + name: "iPhone 14", + physicalSize: Size(1170, 2532), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone15 = DeviceConfiguration( + name: "iPhone 15", + physicalSize: Size(1179, 2556), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone15Pro = DeviceConfiguration( + name: "iPhone 15 Pro", + physicalSize: Size(1179, 2556), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone15ProMax = DeviceConfiguration( + name: "iPhone 15 Pro Max", + physicalSize: Size(1290, 2796), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone16 = DeviceConfiguration( + name: "iPhone 16", + physicalSize: Size(1179, 2556), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone16Pro = DeviceConfiguration( + name: "iPhone 16 Pro", + physicalSize: Size(1206, 2622), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone16ProMax = DeviceConfiguration( + name: "iPhone 16 Pro Max", + physicalSize: Size(1320, 2868), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone16e = DeviceConfiguration( + name: "iPhone 16e", + physicalSize: Size(1170, 2532), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone17 = DeviceConfiguration( + name: "iPhone 17", + physicalSize: Size(1206, 2622), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration iPhone17ProMax = DeviceConfiguration( + name: "iPhone 17 Pro Max", + physicalSize: Size(1320, 2868), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration samsungGalaxyA06 = DeviceConfiguration( + name: "Samsung Galaxy A06", + physicalSize: Size(720, 1600), + devicePixelRatio: 2, + ); + + static const DeviceConfiguration samsungGalaxyA14 = DeviceConfiguration( + name: "Samsung Galaxy A14", + physicalSize: Size(1080, 2408), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration samsungGalaxyA15 = DeviceConfiguration( + name: "Samsung Galaxy A15", + physicalSize: Size(1080, 2340), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration samsungGalaxyA16 = DeviceConfiguration( + name: "Samsung Galaxy A16", + physicalSize: Size(1080, 2340), + devicePixelRatio: 3, + ); + + static const DeviceConfiguration samsungGalaxyS24Ultra = DeviceConfiguration( + name: "Samsung Galaxy S24 Ultra", + physicalSize: Size(1440, 3120), + devicePixelRatio: 3.75, + ); + + static const DeviceConfiguration samsungGalaxyS25Ultra = DeviceConfiguration( + name: "Samsung Galaxy S25 Ultra", + physicalSize: Size(1440, 3120), + devicePixelRatio: 3.75, + ); + + const TestDevices._(); +} + +/// A test device viewport configuration for a [WidgetTester]. +class DeviceConfiguration { + const DeviceConfiguration({ + required this.name, + required this.physicalSize, + required this.devicePixelRatio, + }); + + /// A human-readable device name. + final String name; + + /// The device display size in physical pixels, in portrait orientation. + final Size physicalSize; + + /// The number of physical pixels for each logical pixel. + final double devicePixelRatio; + + /// The device display size in logical pixels, in portrait orientation. + Size get logicalSize => physicalSize / devicePixelRatio; +} diff --git a/pubspec.yaml b/pubspec.yaml index 125d6af..08e7b13 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter-bounty-hunters/flutter_test_robots environment: sdk: ">=2.17.0 <4.0.0" - flutter: ">=1.17.0" + flutter: ">=3.10.0" dependencies: flutter: diff --git a/test/device_configurations_test.dart b/test/device_configurations_test.dart new file mode 100644 index 0000000..5f904ee --- /dev/null +++ b/test/device_configurations_test.dart @@ -0,0 +1,37 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_test_robots/src/device_configurations.dart'; + +void main() { + group("Device Configurations >", () { + testWidgets("configures a named iPhone device", (tester) async { + tester.asIPhone16ProMax(); + + expect(tester.view.physicalSize, const Size(1320, 2868)); + expect(tester.view.devicePixelRatio, 3); + expect(tester.view.physicalSize / tester.view.devicePixelRatio, const Size(440, 956)); + }); + + testWidgets("configures a named Samsung device", (tester) async { + tester.asSamsungGalaxyA16(); + + expect(tester.view.physicalSize, const Size(1080, 2340)); + expect(tester.view.devicePixelRatio, 3); + expect(tester.view.physicalSize / tester.view.devicePixelRatio, const Size(360, 780)); + }); + + testWidgets("configures an arbitrary device", (tester) async { + tester.configureForDevice( + const DeviceConfiguration( + name: "Test Phone", + physicalSize: Size(1000, 2000), + devicePixelRatio: 2.5, + ), + ); + + expect(tester.view.physicalSize, const Size(1000, 2000)); + expect(tester.view.devicePixelRatio, 2.5); + }); + }); +}