1

I have this method here as an extension on the String class which calculates size of text before rendering:

Size textSize({
    required TextDirection textDirection,
    required double textScaleFactor,
    required double minWidth,
    required double maxWidth,
    TextStyle? textStyle,
    int? maxLines,
    TextAlign? textAlign,
    Locale? locale,
    String? ellipsis,
    StrutStyle? strutStyle,
    TextHeightBehavior? textHeightBehavior,
    TextWidthBasis? textWidthBasis,
  }) {
    final textSpan = TextSpan(
      text: this,
      style: textStyle,
      locale: locale,
    );
    final TextPainter tp = TextPainter(
      textDirection: textDirection,
      textScaleFactor: textScaleFactor,
      text: textSpan,
      maxLines: maxLines,
      textAlign: textAlign ?? TextAlign.start,
      locale: locale,
      ellipsis: ellipsis,
      strutStyle: strutStyle,
      textHeightBehavior: textHeightBehavior,
      textWidthBasis: textWidthBasis ?? TextWidthBasis.parent,
    )..layout(
        minWidth: minWidth,
        maxWidth: maxWidth,
      );
    return tp.size;

This method works and gives the expected results when running in the app. But when I call it in my test, with the same parameters I used in the app, it is giving different results.

I tried diving into the inner code of it but couldn't figure out what's the difference between my test and app that's causing the problem. So anyone can explain to me why is it giving two different results for the same parameters and how to solve it?

note: The parameters I tried are

                          text: "Type the text here:",
                          textDirection: Directionality.of(context),
                          minWidth: 0.0,
                          maxWidth: moc.size.width,
                          textScaleFactor: 1.0,
                          textStyle: const widgets.TextStyle(fontSize: 30, color: Colors.black),

The size it is giving in the normal app is Size(258.0, 34.0) and in the test it is giving Size(570.0, 30.0).

I know there is some implicit different parameters between the test and normal app but I can't figure out which one it is. Even setting screen size in the test didn't work.

HII
  • 3,420
  • 1
  • 14
  • 35
  • most likely in app you have `DefaultTextStyle` widget up in the tree hierarchy, btw why at all you need such method? – pskink Jul 17 '22 at 16:39
  • @pskink I'll check that out, I need it because I am writing an photo editing app for desktop where I keep a reference to components (texts, images) in a model class, and one thing I need is their size that will help me do other calculations like where to place them and how to decorate them and .,.. – HII Jul 17 '22 at 16:41
  • if you need such fancy stuff check `CustomMultiChildLayout` - maybe it could be helpful - within the delegate class you can check each child size before setting it's position – pskink Jul 17 '22 at 17:01
  • @pskink I can't use `CustomMultiChildLayout` due to the structure and layers of the app, and I can't find any `DefaultTextStyle` widget in the tree (not a one that I put, idk about any default ones that may be there) do you know where could the problem be? – HII Jul 17 '22 at 17:15
  • i thought that `TextPainter` somehow finds the default text size (defined with `DefaultTextStyle`) but it doesn't seem so since `TextPainter` does not use any `BuildContext` - i have no idea how it works then ... :-( – pskink Jul 17 '22 at 17:35
  • @Dabbel no not yet – HII May 18 '23 at 14:11

1 Answers1

0
  1. The sizes differ, as unit tests seem to use an internal font for testing -- which differs from the default Android font.

  2. The sizes differ as line height in unit tests should match the line height of Flutter Android. Unit tests seemt to use a line height of 1.2, whereas Flutter Android seems to use 1.17 in my case.

Solution

Load a custom font and use it for testing:

final Future<ByteData> data = rootBundle.load('assets/Roboto-Black.ttf');
final FontLoader fontLoader = FontLoader('myDefaultFont')..addFont(data);
await fontLoader.load();
...
const styleCustom = TextStyle(fontFamily: 'myDefaultFont');

Complete Example with and without Custom Font

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  test('textpainter', () async {
    final Future<ByteData> data = rootBundle.load('assets/Roboto-Black.ttf');
    final FontLoader fontLoader = FontLoader('myDefaultFont')..addFont(data);
    await fontLoader.load();

    const style = TextStyle(fontSize: 100);
    const text = Text('test', style: style);

    const styleCustom = const TextStyle(
      fontFamily: 'Roboto',
      fontSize: 100,
      fontWeight: FontWeight.w400,
      textBaseline: TextBaseline.alphabetic,
      decoration: TextDecoration.none,
      height: 1.17,
    );

    const textCustom = Text('test', style: styleCustom);

    var painter = _getPainter(text, style);
    print(painter.size);

    final painterCustom = _getPainter(textCustom, styleCustom);
    print(painterCustom.size);

    expect(painter.size, const Size(400, 100));
    expect(painterCustom.size, const Size(175, 117));
  });
}

_getPainter(Text text, TextStyle style) {
  final TextPainter painter = TextPainter(
    text: TextSpan(children: [TextSpan(text: text.data!, style: style)]),
    textDirection: text.textDirection ?? TextDirection.ltr,
    maxLines: text.maxLines,
    textScaleFactor: text.textScaleFactor ?? 1.0,
    locale: text.locale,
    textAlign: text.textAlign ?? TextAlign.start,
    textHeightBehavior: text.textHeightBehavior,
    textWidthBasis: text.textWidthBasis ?? TextWidthBasis.parent,
  );

  painter.layout();
  return painter;
}
Dabbel
  • 2,468
  • 1
  • 8
  • 25