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;
}