Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

request: different screen size #37

Closed
1 task done
Lyokone opened this issue Mar 22, 2022 · 6 comments
Closed
1 task done

request: different screen size #37

Lyokone opened this issue Mar 22, 2022 · 6 comments
Assignees
Labels
enhancement New feature or request

Comments

@Lyokone
Copy link

Lyokone commented Mar 22, 2022

Is there an existing feature request for this?

  • 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

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

Additional context and comments

No response

@Giuspepe
Copy link
Contributor

Giuspepe commented Mar 24, 2022

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
foo_widget

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.

@Lyokone
Copy link
Author

Lyokone commented Mar 24, 2022

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!

@Kirpal
Copy link
Collaborator

Kirpal commented Mar 25, 2022

@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?

@jeroen-meijer jeroen-meijer added the enhancement New feature or request label Jun 2, 2022
@jeroen-meijer
Copy link
Collaborator

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!

@Lyokone
Copy link
Author

Lyokone commented Jun 15, 2022

Hello, looks like everything needed is here 😁

@jeremiahlukus
Copy link

@Giuspepe you are awesome thank you for sharing this.

@Kirpal Kirpal closed this as completed Sep 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants