frindlymap/test_ux/shots_test.dart

247 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 화면 점검용 캡처 (일반 시험이 아니다). 폰 크기(360×800)로 주요 화면을 그려 test_ux/shots/*.png 로 남긴다.
// flutter test test_ux/shots_test.dart --update-goldens
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:friendly_map/modules/app/app.dart';
import 'package:friendly_map/modules/core/core.dart';
import 'package:friendly_map/modules/kinship/kinship.dart';
import 'package:friendly_map/modules/platform/platform.dart';
import 'package:friendly_map/modules/storage/storage.dart';
import 'package:friendly_map/modules/transport/transport.dart';
import 'package:friendly_map/modules/ui/src/screens/chat_room_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/contact_import_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/career_history_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/circle_group_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/circle_hub_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/family_tree_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/group_detail_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/person_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/requests_screen.dart';
import 'package:friendly_map/modules/ui/src/screens/settings_screen.dart';
import 'package:friendly_map/modules/ui/ui.dart';
import '../test/support/app_device.dart';
import '../test/support/fake_cipher.dart';
import '../test/support/storage_contract.dart' show FakeProbe;
import '../test/widget_test.dart' show FakeBootstrap;
class _Contacts implements ContactsProvider {
@override
Future<bool> requestPermission() async => true;
@override
Future<List<DeviceContact>> all() async => const [
DeviceContact(id: '1', name: '김민수', phones: ['010-3333-4444'], labels: ['가족']),
DeviceContact(id: '2', name: '이영희', phones: ['010-5555-6666'], labels: ['고등학교 동창']),
DeviceContact(id: '3', name: '박철수', phones: ['010-7777-8888'], labels: ['회사']),
DeviceContact(id: '4', name: '최지은', phones: ['010-1212-3434']),
DeviceContact(id: '5', name: '정우성', phones: ['02-555-1234']),
];
}
Future<void> _font(String family, String path) async {
final bytes = File(path).readAsBytesSync();
await (FontLoader(family)..addFont(Future.value(ByteData.view(Uint8List.fromList(bytes).buffer)))).load();
}
void main() {
late AppDevice me, minsu;
late FriendlyApp app;
setUpAll(() async {
await _font('Roboto', 'build/ux/kr.ttf');
await _font('MaterialIcons', '/sdks/flutter/bin/cache/artifacts/material_fonts/MaterialIcons-Regular.otf');
});
Future<void> seed(WidgetTester tester) async {
await tester.runAsync(() async {
final hub = MemoryHub();
me = await AppDevice.create(hub, '010-1111-2222', '+821011112222', '이미영', contacts: _Contacts());
minsu = await AppDevice.create(hub, '010-3333-4444', '+821033334444', '김민수');
app = me.app;
await app.friends.add(minsu.phone, minsu.name);
await AppDevice.settleAll();
await minsu.app.friends.accept(me.phone);
await AppDevice.settleAll();
for (final (p, n) in [('+821055556666', '이영희'), ('+821077778888', '박철수'), ('+821012123434', '최지은')]) {
await app.friends.add(p, n);
}
final people = {for (final p in await app.friends.people()) p.name: p.id};
await app.relate.into(people['이영희']!, 'school.high');
final hb = (await app.groups.add('한빛전자', CardKinds.company, parent: CardKinds.company))!;
final team = (await app.groups.add('결제팀', CardKinds.company, parent: hb.id))!;
await app.relate.into(people['박철수']!, team.id);
await app.relate.into(people['김민수']!, team.id);
final room = (await app.chats.openDirect(people['김민수']!, CardKinds.personal))!;
await app.chats.send(room.id, '안녕하세요, 내일 회의 몇 시죠?');
await app.work.assign(room.id, '결제 API 정리', assigneeId: people['김민수'], due: DateTime(2026, 10, 30));
await app.work.urgent(room.id, '결제 서버가 멈췄어요');
// 가족: 나(남) · 아내 · 아들 · 부모 · 할아버지 · 장인
await app.kin.setMe(gender: Gender.male, birthYear: 1990);
Future<String> rel(String n, Gender g) async => (await app.kin.addRelative(n, gender: g))!.id;
final dad = await rel('이정훈', Gender.male);
await app.relate.asFamily(dad, link: KinLink.parent, gender: Gender.male);
final mom = await rel('박순자', Gender.female);
await app.relate.asFamily(mom, link: KinLink.parent, gender: Gender.female);
await app.kin.setSpouse(dad, mom);
await app.relate.asFamily(await rel('이만복', Gender.male), link: KinLink.parent, to: dad, gender: Gender.male);
final wife = await rel('최유진', Gender.female);
await app.relate.asFamily(wife, link: KinLink.spouse, gender: Gender.female);
await app.relate.asFamily(await rel('최상철', Gender.male), link: KinLink.parent, to: wife, gender: Gender.male);
final son = await rel('이도윤', Gender.male);
await app.relate.asFamily(son, link: KinLink.child, gender: Gender.male);
await app.kin.setParent(son, wife);
await app.facts.save(son, (await app.facts.get(son)).copyWith(birthYear: 2020, birthMonth: 11, birthDay: 3));
// 학교: 고등학교 동문 여럿 (연도·반·대화)
final mineCards = await app.cards.mine();
await app.cards.edit(mineCards.firstWhere((c) => c.kind == CardKinds.school).id, fields: {'name': '이미영', 'school': '가람고', 'year': '2006'});
final cls = (await app.groups.add('27회 3반', CardKinds.school, parent: 'school.high'))!;
var seq = 0;
for (final (nm, yr) in [('정수빈', 2004), ('한지민', 2005), ('오세훈', 2006), ('윤아름', 2006), ('강동원', 2007), ('송혜교', 2007), ('유재석', 2008), ('조인성', 2003)]) {
final p = (await app.friends.add('+82107000${(1000 + seq++)}', nm))!;
await app.deps.storage.receivedCards.put(ReceivedCard(id: 'sc$seq', personId: p.id, kind: CardKinds.school, fieldsJson: encodeFields({'year': '$yr'}), receivedAt: 1, updatedAt: 1));
await app.groups.addMembers('school.high', [p.id]);
if (yr == 2006) await app.groups.addMembers(cls.id, [p.id]);
final room = (await app.chats.openDirect(p.id, CardKinds.personal))!;
for (var i = 0; i < (seq * 2) % 7; i++) {
await app.chats.send(room.id, '안녕 $i');
}
}
// 회사: 한빛전자 직원들 (직책·대화)
await app.cards.edit(mineCards.firstWhere((c) => c.kind == CardKinds.company).id, fields: {'name': '이미영', 'company': '한빛전자', 'position': '과장'});
final all = await app.groups.all();
final hb2 = all.firstWhere((v) => v.group.name == '한빛전자').group;
final pay = all.firstWhere((v) => v.group.name == '결제팀').group;
for (final (nm, pos, g2) in [('김부장', '부장', hb2.id), ('박차장', '차장', pay.id), ('최과장', '과장', pay.id), ('정대리', '대리', pay.id), ('한사원', '사원', pay.id), ('오사원', '사원', hb2.id)]) {
final p = (await app.friends.add('+82108000${(1000 + seq++)}', nm))!;
await app.deps.storage.receivedCards.put(ReceivedCard(id: 'cc$seq', personId: p.id, kind: CardKinds.company, fieldsJson: encodeFields({'company': '한빛전자', 'position': pos}), receivedAt: 1, updatedAt: 1));
await app.groups.addMembers(g2, [p.id]);
final room = (await app.chats.openDirect(p.id, CardKinds.personal))!;
for (var i = 0; i < (seq * 3) % 6; i++) {
await app.chats.send(room.id, '안녕 $i');
}
}
// 김민수가 새 폰으로 다시 연결을 청함
final again = await AppDevice.create(hub, '010-3333-4444', '+821033334444', '김민수 새폰');
await again.app.friends.add(me.phone, '이미영');
await AppDevice.settleAll();
});
}
Future<void> open(WidgetTester tester) async {
tester.view.physicalSize = const Size(1080, 2400);
tester.view.devicePixelRatio = 3;
addTearDown(tester.view.reset);
await seed(tester);
await tester.pumpWidget(FriendlyMapApp(bootstrap: FakeBootstrap(keys: true, app: app)));
await tester.pumpAndSettle();
}
Future<void> shot(WidgetTester tester, String name) async {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
await tester.pumpAndSettle();
await expectLater(find.byType(MaterialApp), matchesGoldenFile('shots/$name.png'));
}
Future<void> push(WidgetTester tester, Widget page) async {
tester.state<NavigatorState>(find.byType(Navigator).first).push(MaterialPageRoute<void>(builder: (_) => page));
await tester.pumpAndSettle();
}
Future<void> tab(WidgetTester tester, String label) async {
await tester.tap(find.descendant(of: find.byType(NavigationBar), matching: find.text(label)));
await tester.pumpAndSettle();
}
testWidgets('처음 시작', (tester) async {
tester.view.physicalSize = const Size(1080, 2400);
tester.view.devicePixelRatio = 3;
addTearDown(tester.view.reset);
final fresh = FriendlyApp(AppDeps(storage: Storage.memory(), transport: MemoryTransport('+82100', MemoryHub()), cipher: FakeCipher('K'), identity: FakeIdentity('K'), probe: FakeProbe(10)));
await tester.pumpWidget(FriendlyMapApp(bootstrap: FakeBootstrap(app: fresh)));
await tester.pumpAndSettle();
await shot(tester, '00_onboarding');
});
testWidgets('아래 메뉴 다섯 칸', (tester) async {
await open(tester);
await shot(tester, '01_chats');
await tab(tester, '관계');
await shot(tester, '02_relations');
await tab(tester, '업무');
await shot(tester, '03_tasks');
await tab(tester, '명함');
await shot(tester, '04_cards');
await tab(tester, '나');
await shot(tester, '05_me');
});
testWidgets('요청 · 그룹 · 설정', (tester) async {
await open(tester);
await push(tester, const RequestsScreen());
await shot(tester, '10_requests');
await push(tester, const GroupDetailScreen(groupId: CardKinds.school));
await shot(tester, '11_group_school');
await push(tester, const GroupDetailScreen(groupId: CardKinds.company));
await shot(tester, '12_group_company');
await push(tester, const SettingsScreen());
await shot(tester, '13_settings');
await push(tester, const FamilyTreeScreen());
await shot(tester, '14_family_tree');
await push(tester, const CircleHubScreen(rootId: CardKinds.school));
await shot(tester, '15_school_hub');
await push(tester, const CircleGroupScreen(groupId: 'school.high'));
await shot(tester, '16_school_ring');
await tester.tap(find.text('목록'));
await tester.pumpAndSettle();
await shot(tester, '17_school_list');
await tester.tap(find.text('고르기'));
await tester.pumpAndSettle();
await tester.tap(find.textContaining('모두'));
await tester.pumpAndSettle();
await shot(tester, '18_school_pick');
await push(tester, const CircleHubScreen(rootId: CardKinds.company));
await shot(tester, '19_company_hub');
final minsuId = (await tester.runAsync(() async => (await app.friends.people()).firstWhere((p) => p.name == '김민수').id))!;
await tester.runAsync(() async {
await app.career.seed(); // 위에서 넣은 직원들의 이력
// 김민수는 한빛전자(2019~2024) → 누리소프트로 옮김
Future<void> card(String company) async {
await app.deps.storage.receivedCards.put(ReceivedCard(
id: 'mc', personId: minsuId, kind: CardKinds.company, fieldsJson: encodeFields({'company': company, 'position': '팀장'}), receivedAt: 1, updatedAt: 9));
await app.career.onCard('mc');
}
await card('한빛전자');
await card('누리소프트');
await app.career.setYears(minsuId, 0, from: 2024, current: true);
await app.career.setYears(minsuId, 1, from: 2019, to: 2024);
});
await push(tester, CareerHistoryScreen(personId: minsuId));
await shot(tester, '19c_career_history');
await push(tester, const CircleGroupScreen(groupId: 'g1'));
await tester.tap(find.text('목록'));
await tester.pumpAndSettle();
await shot(tester, '19b_company_list');
});
testWidgets('주소록 · 사람 · 대화', (tester) async {
await open(tester);
await push(tester, const ContactImportScreen());
await shot(tester, '20_contacts');
final people = {for (final p in (await tester.runAsync(() => app.friends.people()))!) p.name: p.id};
await push(tester, PersonScreen(personId: people['김민수']!));
await shot(tester, '21_person');
await tester.tap(find.text('관계'));
await tester.pumpAndSettle();
await shot(tester, '22_relate');
await tester.tapAt(const Offset(10, 10));
await tester.pumpAndSettle();
final rooms = (await tester.runAsync(() => app.chats.summaries()))!;
await push(tester, ChatRoomScreen(roomId: rooms.first.room.id));
await shot(tester, '23_chat_room');
});
}