Newer
Older
SmTIAS-Capture / test / widgets / preview_orientation_golden_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:smtias_capture/models/preview_region.dart';
import 'package:smtias_capture/widgets/oriented_preview.dart';
import 'package:smtias_capture/widgets/region_crop.dart';

/// プレビューの「向き変換」「クロップ」の見た目をゴールデン画像で固定する回帰テスト.
///
/// リファクタリング前に正解画像を生成・コミットし,リファクタリング後に
/// `flutter test` で再照合する(1px でも変われば失敗).これは「変換を壊して
/// いないか」の高速な自動チェックであり,「向きが本当に正しいか」の最終確認は
/// 実機の全体スクリーンショットと基準画像(local_reference/ 配下,個人情報のため
/// 非コミット)の目視比較で行う.詳細は SPEC_04 参照.
void main() {
  // ゴールデンの基準サイズ(縦長プレビューを模す).
  const size = Size(240, 360);

  // 中央寄りのクロップ領域(表示後座標 0.0–1.0).
  const region = PreviewRegion(left: 0.25, top: 0.2, right: 0.75, bottom: 0.8);

  testWidgets('OrientedPreview: 向き変換が正解画像と一致する', (tester) async {
    await _pump(tester, size, const OrientedPreview(child: _TestPattern()));
    await expectLater(
      find.byKey(_targetKey),
      matchesGoldenFile('goldens/oriented_preview.png'),
    );
  });

  testWidgets('RegionCrop: クロップ拡大が正解画像と一致する', (tester) async {
    await _pump(
      tester,
      size,
      const RegionCrop(region: region, child: _TestPattern()),
    );
    await expectLater(
      find.byKey(_targetKey),
      matchesGoldenFile('goldens/region_crop.png'),
    );
  });

  testWidgets('向き変換 + クロップ: パイプライン全体が正解画像と一致する', (tester) async {
    await _pump(
      tester,
      size,
      const RegionCrop(
        region: region,
        child: OrientedPreview(child: _TestPattern()),
      ),
    );
    await expectLater(
      find.byKey(_targetKey),
      matchesGoldenFile('goldens/preview_pipeline.png'),
    );
  });
}

const _targetKey = ValueKey('golden-target');

/// [child] を固定サイズの [RepaintBoundary] で包んでレンダリングする.
Future<void> _pump(WidgetTester tester, Size size, Widget child) async {
  await tester.pumpWidget(
    Center(
      child: RepaintBoundary(
        key: _targetKey,
        child: SizedBox.fromSize(size: size, child: child),
      ),
    ),
  );
}

/// 上下左右が一目で判別できる非対称テストパターン(向き・クロップ確認用の入力).
///
/// 上=赤 / 下=青 / 左=緑 / 右=黄 の色帯と,左上寄りのマゼンタ円を描く.
/// 180°回転すれば色帯の上下・左右が入れ替わり,円が右下へ移動するため,
/// 反転や座標の取り違えがゴールデン差分として確実に現れる.
class _TestPattern extends StatelessWidget {
  const _TestPattern();

  @override
  Widget build(BuildContext context) {
    return CustomPaint(painter: _TestPatternPainter(), size: Size.infinite);
  }
}

class _TestPatternPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final w = size.width;
    final h = size.height;
    final t = (w < h ? w : h) * 0.15;

    void fill(Rect rect, Color color) {
      canvas.drawRect(rect, Paint()..color = color);
    }

    fill(Offset.zero & size, const Color(0xFFFFFFFF)); // 背景:白
    fill(Rect.fromLTWH(0, 0, w, t), const Color(0xFFE53935)); // 上:赤
    fill(Rect.fromLTWH(0, h - t, w, t), const Color(0xFF1E88E5)); // 下:青
    fill(Rect.fromLTWH(0, 0, t, h), const Color(0xFF43A047)); // 左:緑
    fill(Rect.fromLTWH(w - t, 0, t, h), const Color(0xFFFDD835)); // 右:黄

    // 左上寄りのマーカー円(両軸反転とクロップ中心を判別する).
    canvas.drawCircle(
      Offset(w * 0.3, h * 0.3),
      t * 0.6,
      Paint()..color = const Color(0xFFD500F9),
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}