import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/ui/secret_field.dart'; /// W 공용 위젯 약속 (SecretField): 처음은 늘 가림 · 눈 단추는 눌러 켜고 끄기 (누르는 동안만이 아님) · 자동 고침 · 추천 끔 · /// 바깥 가리기 하나로 칸 여럿 · 좁은 폭 · 큰 글자에서 넘치지 않음. 위젯 하나에 대해 한 번만 둔다 (설계 7-2) void main() { TextField field(WidgetTester t, int i) => t.widget(find.byType(TextField).at(i)); EditableText edit(WidgetTester t, int i) => t.widget(find.byType(EditableText).at(i)); Future show(WidgetTester t, Widget child, {double width = 412, double scale = 1}) async { t.view.physicalSize = Size(width, 900); t.view.devicePixelRatio = 1; addTearDown(t.view.reset); await t.pumpWidget(MaterialApp( home: MediaQuery( data: MediaQueryData(size: Size(width, 900), textScaler: TextScaler.linear(scale)), child: Scaffold(body: Column(children: [child])), ), )); } testWidgets('스스로 들기: 처음 가림 → 누르면 보임 (손을 떼도 그대로) → 다시 누르면 가림', (t) async { final c = TextEditingController(text: 'pw'); await show(t, SecretField(controller: c, label: '비밀번호')); expect(field(t, 0).obscureText, isTrue); expect(field(t, 0).autocorrect, isFalse); expect(field(t, 0).enableSuggestions, isFalse); expect(find.byTooltip('보이기'), findsOneWidget); expect(find.byIcon(Icons.visibility_outlined), findsOneWidget); await t.tap(find.byType(IconButton)); await t.pump(); expect(field(t, 0).obscureText, isFalse, reason: '누른 뒤 손을 떼도 보임'); expect(find.byTooltip('가리기'), findsOneWidget); expect(find.byIcon(Icons.visibility_off_outlined), findsOneWidget); await t.tap(find.byType(IconButton)); await t.pump(); expect(field(t, 0).obscureText, isTrue); expect(edit(t, 0).controller.text, 'pw', reason: '보이기 · 가리기는 글을 바꾸지 않음'); }); testWidgets('바깥 가리기: 눈 단추 하나가 두 칸을 함께, 단추 없는 칸은 따름', (t) async { var hidden = true; await show( t, StatefulBuilder( builder: (ctx, set) => Column(children: [ SecretField(controller: TextEditingController(), label: 'a', hidden: hidden, onToggle: () => set(() => hidden = !hidden)), SecretField(controller: TextEditingController(), label: 'b', hidden: hidden), ]), )); expect(find.byType(IconButton), findsOneWidget, reason: '단추는 첫 칸에만'); expect([field(t, 0).obscureText, field(t, 1).obscureText], [true, true]); await t.tap(find.byType(IconButton)); await t.pump(); expect([field(t, 0).obscureText, field(t, 1).obscureText], [false, false]); }); testWidgets('오류 글 · 도움 글이 칸 아래에 보임', (t) async { await show(t, SecretField(controller: TextEditingController(), label: '다시 넣기', helper: '없으면 비웁니다', errorText: '두 비밀번호가 다릅니다')); expect(find.text('두 비밀번호가 다릅니다'), findsOneWidget); }); for (final width in [300.0, 412.0]) { for (final scale in [1.0, 2.0]) { testWidgets('폭 $width · 배율 $scale: 긴 이름 · 도움 글도 넘치지 않음', (t) async { await show( t, SecretField( controller: TextEditingController(), label: 'API key or username:password', helper: 'If set, encrypts with AES-256. Leave empty for a plain ZIP archive file'), width: width, scale: scale); expect(t.takeException(), isNull); expect(t.getRect(find.byType(IconButton)).right, lessThanOrEqualTo(width)); }); } } }