alchemist
alchemist copied to clipboard
request: different screen size
Is there an existing feature request for this?
- [X] I have searched the existing issues.
Command
I would love if I could run my tests with a lot of screen sizes (iPhone / iPad / desktop)
Description
I would love if I could generate file the same way golden toolkit is doing
![Screenshot 2022-03-22 at 16 17 53](https://user-images.githubusercontent.com/3680002/159516763-2d183c43-7a8d-4917-b555-75246a8a48d4.png)
Reasoning
Since variant is used for the platform, I cannot find a way to keep the device type properly organized with Alchemist. Variants are useful to get also a nice overview in IDEs.
![Screenshot 2022-03-22 at 16 25 39](https://user-images.githubusercontent.com/3680002/159517878-7fb77b51-200d-4030-8d1a-07f1ba8a10ed.png)
Additional context and comments
No response
Hey @Lyokone,
I have written a GoldenTestDeviceScenario
widget which takes a Device
containing the device configuration to achieve this. The Device
is basically the same class as the one from eBay's golden toolkit. See the following code:
golden_test_device_scenario.dart
import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';
import 'device.dart';
/// Wrapper for testing widgets (primarily screens) with device constraints
class GoldenTestDeviceScenario extends StatelessWidget {
final String name;
final Device device;
final ValueGetter<Widget> builder;
const GoldenTestDeviceScenario({
required this.name,
required this.builder,
this.device = Device.iphone11,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GoldenTestScenario(
name: '${this.name} (device: ${device.name})',
child: ClipRect(
child: MediaQuery(
data: MediaQuery.of(context).copyWith(
size: this.device.size,
padding: this.device.safeArea,
platformBrightness: this.device.brightness,
devicePixelRatio: this.device.devicePixelRatio,
textScaleFactor: this.device.textScaleFactor,
),
child: SizedBox(
height: this.device.size.height,
width: this.device.size.width,
child: builder(),
),
),
),
);
}
}
device.dart
// Copied and adapted from https://github.com/eBay/flutter_glove_box/blob/master/packages/golden_toolkit/lib/src/device.dart
import 'package:flutter/material.dart';
/// This [Device] is a configuration for golden test.
class Device {
/// This [Device] is a configuration for golden test.
const Device({
required this.size,
required this.name,
this.devicePixelRatio = 1.0,
this.textScaleFactor = 1.0,
this.brightness = Brightness.light,
this.safeArea = const EdgeInsets.all(0),
});
/// [smallPhone] one of the smallest phone screens
static const Device smallPhone =
Device(name: 'small_phone', size: Size(375, 667));
/// [iphone11] matches specs of iphone11, but with lower DPI for performance
static const Device iphone11 = Device(
name: 'iphone11',
size: Size(414, 896),
devicePixelRatio: 1.0,
safeArea: EdgeInsets.only(top: 44, bottom: 34),
);
static const Device iphone11Landscape = Device(
name: 'iphone11_landscape',
size: Size(896, 414),
devicePixelRatio: 1.0,
safeArea: EdgeInsets.only(left: 44, right: 34),
);
/// [tabletLandscape] example of tablet that in landscape mode
static const Device tabletLandscape =
Device(name: 'tablet_landscape', size: Size(1366, 1024));
/// [tabletPortrait] example of tablet that in portrait mode
static const Device tabletPortrait =
Device(name: 'tablet_portrait', size: Size(1024, 1366));
/// [name] specify device name. Ex: Phone, Tablet, Watch
final String name;
/// [size] specify device screen size. Ex: Size(1366, 1024))
final Size size;
/// [devicePixelRatio] specify device Pixel Ratio
final double devicePixelRatio;
/// [textScaleFactor] specify custom text scale factor
final double textScaleFactor;
/// [brightness] specify platform brightness
final Brightness brightness;
/// [safeArea] specify insets to define a safe area
final EdgeInsets safeArea;
/// [copyWith] convenience function for [Device] modification
Device copyWith({
Size? size,
double? devicePixelRatio,
String? name,
double? textScale,
Brightness? brightness,
EdgeInsets? safeArea,
}) {
return Device(
size: size ?? this.size,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
name: name ?? this.name,
textScaleFactor: textScale ?? this.textScaleFactor,
brightness: brightness ?? this.brightness,
safeArea: safeArea ?? this.safeArea,
);
}
/// [dark] convenience method to copy the current device and apply dark theme
Device dark() {
return Device(
size: size,
devicePixelRatio: devicePixelRatio,
textScaleFactor: textScaleFactor,
brightness: Brightness.dark,
safeArea: safeArea,
name: '${name}_dark',
);
}
@override
String toString() {
return 'Device: $name, '
'${size.width}x${size.height} @ $devicePixelRatio, '
'text: $textScaleFactor, $brightness, safe: $safeArea';
}
}
And in the test which you want to run with different device configurations:
import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';
import 'device.dart';
import 'golden_test_device_scenario.dart';
void main() {
Widget buildWidgetUnderTest() => MaterialApp(
home: Scaffold(
body: ListView.builder(
itemBuilder: (context, i) => Text('$i'),
),
),
);
goldenTest(
'golden test',
fileName: 'foo_widget',
builder: () => GoldenTestGroup(
children: [
GoldenTestDeviceScenario(
device: Device.smallPhone,
name: 'golden test FooWidget on small phone',
builder: buildWidgetUnderTest,
),
GoldenTestDeviceScenario(
device: Device.tabletLandscape,
name: 'golden test FooWidget on tablet',
builder: buildWidgetUnderTest,
),
],
),
);
}
This test will generate the following golden image
An important thing to note is that you will run into trouble if your image ends up being larger than 2000 x 2000 pixels (e.g. if you run a lot of golden test scenarios with large devices in one GoldenTestGroup
). In that case you have to pass the parameter constraints: BoxConstraints.loose(<expected size of your image>)
to goldenTest
.
@definitelyokay @Kirpal I can create a PR containing this code if you think it makes sense.
Wow! Such an insightful response!! Thanks a lot 😍 I'll try that tomorrow on my main project! But I think it answers all my questions!
@Giuspepe that's pretty cool! A pull request would definitely be appreciated. Do you think it's worth revisiting the resizing/constraints logic at the same time to avoid using hacky constraints
values? I can't tell how hacky your workaround ends up being. What do you think?
A PR is currently in the pipeline related to this functionality, #59.
@Giuspepe @Kirpal @Lyokone, Seeing as we can define scenario-specific constraints once that PR has merged, and you can already use custom widgets as @Giuspepe has shown, can we considered this issue fixed? 👀
If not, feel free to comment and let us know if there's anything you'd like us to have a look at!
Hello, looks like everything needed is here 😁
@Giuspepe you are awesome thank you for sharing this.