generated from ttipo/project-base
fix(flutter): 쓰기 · 검색 입력칸에 테마의 둥근 테두리가 그려지던 것을 고치고 칸 줄을 보드에 맞춘다
InputDecoration.collapsed 는 enabledBorder · focusedBorder 를 비워 두어 TextField 가 앱 테마의 둥근 테두리로 채운다. 그래서 받는 사람 · 제목 · 본문 · 검색 칸에 글자를 감싼 테두리가 그려지고 초점이면 칸 줄의 아래 선과 청록 테두리가 겹쳤다 (11-12 배포 뒤 사용자 보고). 여섯 테두리를 모두 없앤 borderlessInputDecoration 으로 바꾸고, lib 에 InputDecoration.collapsed 가 다시 들어오면 시험이 실패하게 한다. 함께 칸 줄을 보드 App1Compose 에 맞췄다: 칸 이름 15 · 간격 6 · 오른쪽 여백 4, 칩이 여러 줄이면 칸 이름과 [참조 펼치기]를 첫 칩 줄에, 안내 글 색 · 칩 ✕ 색, 본문 칸 최소 높이(원본 메일이 있으면 150 · 없으면 서명이 있어도 240). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../support/fakes.dart';
|
||||
import '../../support/input_borders.dart';
|
||||
import '../../support/login_fakes.dart';
|
||||
import '../../support/mail_fakes.dart';
|
||||
import '../../support/mail_harness.dart';
|
||||
@@ -187,6 +188,24 @@ void main() {
|
||||
expect(h.mail.searchQueries, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'검색 칸은 테두리를 그리지 않는다 — 초점이 있을 때 · 놓은 뒤(앱 테마의 둥근 테두리를 물려받지 않는다)',
|
||||
(WidgetTester tester) async {
|
||||
await openSearch(tester);
|
||||
expect(
|
||||
expectBorderlessInput(tester, field(), reason: '초점').isFocused,
|
||||
isTrue,
|
||||
);
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
expectBorderlessInput(tester, field(), reason: '평소').isFocused,
|
||||
isFalse,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'[검색] → 뼈대("찾는 중") → 결과 — 개수 줄 · 제목 · 미리 보기의 맞은 말이 노랑, 자판을 내린다',
|
||||
(WidgetTester tester) async {
|
||||
|
||||
@@ -32,6 +32,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../support/compose_fakes.dart';
|
||||
import '../../support/fakes.dart';
|
||||
import '../../support/input_borders.dart';
|
||||
import '../../support/login_fakes.dart';
|
||||
import '../../support/mail_fakes.dart';
|
||||
import '../../support/mail_harness.dart';
|
||||
@@ -500,6 +501,241 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('입력칸 모양', () {
|
||||
testWidgets(
|
||||
'칸마다 테두리를 그리지 않는다 — 평소 · 초점 · 꺼짐(보내는 중) 모두 칸 줄의 아래 선뿐(앱 테마의 둥근 테두리를 물려받지 않는다)',
|
||||
(WidgetTester tester) async {
|
||||
await start(tester);
|
||||
await open(tester, MailRoutes.compose);
|
||||
await tapTooltip(tester, MailComposeTexts.expandCopies);
|
||||
await addRecipient(tester, 'doyun@x.kr');
|
||||
await tester.enterText(subjectField, '계약');
|
||||
await tester.enterText(bodyField, '안녕하세요');
|
||||
final List<(String, Finder)> fields = <(String, Finder)>[
|
||||
('받는 사람', field(RecipientFieldKind.to)),
|
||||
('참조', field(RecipientFieldKind.cc)),
|
||||
('숨은참조', field(RecipientFieldKind.bcc)),
|
||||
('제목', subjectField),
|
||||
('본문', bodyField),
|
||||
];
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
for (final (String name, Finder finder) in fields) {
|
||||
final InputDecorator decorator = expectBorderlessInput(
|
||||
tester,
|
||||
finder,
|
||||
reason: '$name 평소',
|
||||
);
|
||||
expect(decorator.isFocused, isFalse);
|
||||
expect(decorator.decoration.enabled, isTrue);
|
||||
}
|
||||
|
||||
for (final (String name, Finder finder) in fields) {
|
||||
tester.widget<TextField>(finder).focusNode!.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
final InputDecorator decorator = expectBorderlessInput(
|
||||
tester,
|
||||
finder,
|
||||
reason: '$name 초점',
|
||||
);
|
||||
expect(decorator.isFocused, isTrue);
|
||||
}
|
||||
|
||||
// 보내는 동안은 칸을 막는다
|
||||
final Completer<void> gate = Completer<void>();
|
||||
c.sendGate = gate;
|
||||
await tester.tap(find.byTooltip(MailComposeTexts.send));
|
||||
await tester.pump();
|
||||
for (final (String name, Finder finder) in fields) {
|
||||
final InputDecorator decorator = expectBorderlessInput(
|
||||
tester,
|
||||
finder,
|
||||
reason: '$name 꺼짐',
|
||||
);
|
||||
expect(decorator.decoration.enabled, isFalse);
|
||||
}
|
||||
gate.complete();
|
||||
await tester.pumpAndSettle();
|
||||
expect(c.sends, hasLength(1));
|
||||
},
|
||||
);
|
||||
|
||||
/// 받는 사람 칸 줄
|
||||
Finder toRow() => find.descendant(
|
||||
of: find.byKey(const ValueKey<RecipientFieldKind>(RecipientFieldKind.to)),
|
||||
matching: find.byType(ComposeFieldRow),
|
||||
);
|
||||
Finder removeChip(String name) =>
|
||||
find.byTooltip(MailComposeTexts.remove(name));
|
||||
double centerY(WidgetTester tester, Finder finder) =>
|
||||
tester.getCenter(finder).dy;
|
||||
|
||||
testWidgets(
|
||||
'칸 줄 — 칸 이름 15 · 칸 이름과 칸 사이 6 · 오른쪽 여백 4 · 안내 글은 보드의 placeholder 색 · 칩 ✕ 는 #4A5363 (보드 App1Compose)',
|
||||
(WidgetTester tester) async {
|
||||
await start(tester);
|
||||
await open(
|
||||
tester,
|
||||
MailRoutes.compose,
|
||||
extra: const ComposePrefill(to: <String>['이도윤 <doyun@x.kr>']),
|
||||
);
|
||||
await tapTooltip(tester, MailComposeTexts.expandCopies);
|
||||
|
||||
for (final String label in <String>[
|
||||
fieldLabelOf(RecipientFieldKind.to),
|
||||
fieldLabelOf(RecipientFieldKind.cc),
|
||||
fieldLabelOf(RecipientFieldKind.bcc),
|
||||
MailComposeTexts.subject,
|
||||
]) {
|
||||
// 보드 14 → 앱의 크기 대응 15 (앱 디자인 README 「쓰기」)
|
||||
expect(
|
||||
tester.widget<Text>(find.text(label)).style?.fontSize,
|
||||
15,
|
||||
reason: label,
|
||||
);
|
||||
}
|
||||
final Rect label = tester.getRect(find.text(MailComposeTexts.subject));
|
||||
final Rect subject = tester.getRect(subjectField);
|
||||
final Rect row = tester.getRect(
|
||||
find.ancestor(
|
||||
of: subjectField,
|
||||
matching: find.byType(ComposeFieldRow),
|
||||
),
|
||||
);
|
||||
expect(subject.left, label.left + 72 + 6);
|
||||
expect(subject.right, row.right - 4);
|
||||
expect(row.height, 56);
|
||||
|
||||
// 안내 글 — 보드의 placeholder #8A94A3(검색 칸 안내 글과 같은 색)
|
||||
final InputDecoration? subjectHint = tester
|
||||
.widget<TextField>(subjectField)
|
||||
.decoration;
|
||||
final InputDecoration? bodyHint = tester
|
||||
.widget<TextField>(bodyField)
|
||||
.decoration;
|
||||
expect(subjectHint?.hintStyle?.color, AppColors.borderInput);
|
||||
expect(bodyHint?.hintStyle?.color, AppColors.borderInput);
|
||||
expect(AppColors.borderInput, const Color(0xFF8A94A3));
|
||||
|
||||
final IconButton remove = tester.widget<IconButton>(
|
||||
find.ancestor(
|
||||
of: removeChip('이도윤'),
|
||||
matching: find.byType(IconButton),
|
||||
),
|
||||
);
|
||||
expect(remove.color, AppColors.textSecondary);
|
||||
expect(AppColors.textSecondary, const Color(0xFF4A5363));
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'받는 사람 칩이 여러 줄이면 칸 이름 · [참조 펼치기]는 첫 칩 줄에 맞춘다 — 한 줄이면 줄의 가운데 그대로',
|
||||
(WidgetTester tester) async {
|
||||
await start(tester);
|
||||
await open(
|
||||
tester,
|
||||
MailRoutes.compose,
|
||||
extra: const ComposePrefill(
|
||||
to: <String>[
|
||||
'이도윤 <doyun@x.kr>',
|
||||
'박준호 <junho@x.kr>',
|
||||
'최서연 <seoyeon@x.kr>',
|
||||
],
|
||||
),
|
||||
);
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
final Finder label = find.text(fieldLabelOf(RecipientFieldKind.to));
|
||||
final Finder toggle = find.byTooltip(MailComposeTexts.expandCopies);
|
||||
|
||||
// 두 줄 — 셋째 칩은 아래 줄. 칸 이름 · 단추는 첫 칩 줄의 가운데(줄 전체의 가운데가 아니다)
|
||||
final double firstLine = centerY(tester, removeChip('이도윤'));
|
||||
expect(centerY(tester, removeChip('최서연')), greaterThan(firstLine + 40));
|
||||
expect(
|
||||
centerY(tester, label),
|
||||
moreOrLessEquals(firstLine, epsilon: 0.5),
|
||||
);
|
||||
expect(
|
||||
centerY(tester, toggle),
|
||||
moreOrLessEquals(firstLine, epsilon: 0.5),
|
||||
);
|
||||
expect(
|
||||
tester.getRect(toRow()).top + 4 + 24,
|
||||
moreOrLessEquals(firstLine, epsilon: 0.5),
|
||||
);
|
||||
|
||||
// 한 줄 — 칩 · 칸 이름 · 단추가 줄(56)의 가운데
|
||||
await tester.tap(removeChip('최서연'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(removeChip('박준호'));
|
||||
await tester.pumpAndSettle();
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
final Rect row = tester.getRect(toRow());
|
||||
expect(row.height, 56);
|
||||
for (final Finder finder in <Finder>[
|
||||
label,
|
||||
toggle,
|
||||
removeChip('이도윤'),
|
||||
]) {
|
||||
expect(
|
||||
centerY(tester, finder),
|
||||
moreOrLessEquals(row.center.dy, epsilon: 0.5),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/// 본문 칸의 최소 높이 — 본문 칸을 감싼 ConstrainedBox(가장 가까운 것)
|
||||
Finder bodyBox() => find
|
||||
.ancestor(of: bodyField, matching: find.byType(ConstrainedBox))
|
||||
.first;
|
||||
|
||||
testWidgets('본문 칸 최소 높이 — 새 메일은 서명이 있어도 240, 원본 메일이 있는 답장은 150 (보드)', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
expect(composeBodyMinHeightOf(hasQuote: false), 240);
|
||||
expect(composeBodyMinHeightOf(hasQuote: true), 150);
|
||||
c.signature = <String, Object?>{
|
||||
'bodyHtml': null,
|
||||
'body': '김아라\n경영지원팀',
|
||||
'isForNew': true,
|
||||
'isForReply': true,
|
||||
};
|
||||
h.mail.put(
|
||||
kClientsId,
|
||||
messageJson(
|
||||
'r1',
|
||||
mailboxId: kClientsId,
|
||||
subject: '유지보수 계약',
|
||||
isRead: true,
|
||||
),
|
||||
detail: <String, Object?>{'textBody': '원문 첫 줄'},
|
||||
);
|
||||
await start(tester);
|
||||
await open(tester, MailRoutes.compose);
|
||||
|
||||
expect(find.text('김아라'), findsOneWidget);
|
||||
expect(
|
||||
tester.widget<ConstrainedBox>(bodyBox()).constraints.minHeight,
|
||||
240,
|
||||
);
|
||||
expect(tester.getSize(bodyBox()).height, 240);
|
||||
|
||||
await tapTooltip(tester, MailComposeTexts.close);
|
||||
expect(find.byType(MessageComposePage), findsNothing);
|
||||
await open(tester, MailRoutes.composeReply('r1'));
|
||||
|
||||
expect(find.text('김아라'), findsOneWidget);
|
||||
expect(find.textContaining('이도윤 · '), findsOneWidget);
|
||||
expect(
|
||||
tester.widget<ConstrainedBox>(bodyBox()).constraints.minHeight,
|
||||
150,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('받는 사람', () {
|
||||
testWidgets('쉼표 · [다음]에 칩 — 붙여 넣은 여러 줄도 나누고, 틀린 주소는 빨간 칩, ✕ 로 뺀다', (
|
||||
WidgetTester tester,
|
||||
|
||||
Reference in New Issue
Block a user