Text.richを使用した場合のテストでつまづいたところ

# はじめに
<!-- 発端や概要を記載 -->
こんにちは。OREOです。

本記事ではFlutterでText.richを使用した場合のテストでつまづいたところについて, 備忘録も兼ねて記録しておこうと思います。


# TextWidgetについて
実装の中でText Widgetを使用することはよくあると思いますが， 少し凝ったTextを実装する際にはText.richやRichText Widgetを使用することがあると思います。

今回は下記の様な画面をベースにテストを考えてみたいと思います。
![スクリーンショット 2024-06-12 22.32.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1550392/9853c9d4-2246-6194-58b5-dc13da5f586c.png)

#テスト実行
## A.半角スペースで文字列間の領域を確保する場合
まず初めにシンプルに半角スペースで文字間を確保する場合を見てみます。

### ソースコード
```main.dart
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.black87,
        title: const Text(
          'Test App',
        ),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Text Widgetです',
            ),
            Text.rich(TextSpan(children: [
              TextSpan(
                text: 'RichText Widgetです',
              ),
            ])),
          ],
        ),
      ),
    );
  }
```

### テストコード
```test.dart
void main() {
  testWidgets('Widget Test', (WidgetTester tester) async {
    await tester.pumpWidget(const MyApp());

    expect(find.text('Text Widgetです'), findsOneWidget);
    expect(find.text('RichText Widgetです', findRichText: true), findsOneWidget);
  });
}
```
### テスト結果
```terminal
fvm flutter test
00:02 +1: All tests passed!  
```

テストは問題なく通ります！


## B.SizedBoxで文字列間の領域を確保する場合
まず初めに文字間を明示的に値を決めて確保する場合を見てみます。

### ソースコード
```main.dart
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.black87,
        title: const Text(
          'Test App',
        ),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Text Widgetです',
            ),
            Text.rich(TextSpan(children: [
              TextSpan(
                text: 'RichText',
              ),
              WidgetSpan(
                  child: SizedBox(
                width: 3,
              )),
              TextSpan(
                text: 'Widgetです',
              ),
            ])),
          ],
        ),
      ),
    );
```
### テストコード
```test.dart
void main() {
  testWidgets('Widget Test', (WidgetTester tester) async {
    await tester.pumpWidget(const MyApp());

    expect(find.text('Text Widgetです'), findsOneWidget);
    expect(find.text('RichText Widgetです', findRichText: true), findsOneWidget);
  });
}
```

### テスト結果
```terminal
00:02 +0: Widget Test                                                         
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: exactly one matching node in the widget tree
  Actual: _TextFinder:<zero widgets with text "RichText Widgetです" (ignoring offstage widgets)>
   Which: means none were found but one was expected
```

"RichText Widgetです"なんてないよと怒られてしまいました。
文字間をSizedBoxによって明示的にサイズを指定して確保している場合はテストに落ちる様です。
半角スペースとSizedBoxで確保した部分は根本的に異なるということはなんとなく理解できそうです。

実はSizedBoxで確保した領域は「OBJ置換文字」(Unicode番号:U+FFFC)として取り扱われてしまうためテストに落ちてしまいます。

:::note warn
OBJ置換文字:特殊用途文字
:::

そこでOBJ置換文字を考慮したテストに書き直したいと思います。
```test.dart
void main() {
  testWidgets('Widget Test', (WidgetTester tester) async {
    await tester.pumpWidget(const MyApp());

    expect(find.text('Text Widgetです'), findsOneWidget);
    // 半角スペース「 」でなくOBJ置換文字「￼」を使う
    expect(find.text('RichText￼Widgetです', findRichText: true), findsOneWidget);
  });
}
```

### テスト結果
```terminal
fvm flutter test
00:02 +1: All tests passed!  
```

問題なく通ります。
これで問題なく通る様になりました！👏

ちなみにSizedBoxの値はどんな値を入れても, OBJ置換文字1個分に置き換えられる様です。
# さいごに
SizedBoxがOBJ置換文字として扱われることをに気づくことができず小一時間ほど時間を無駄にしてしまいました。..

本記事が誰かの助けになることを願いまして締めの言葉とさせていただきます！