Files
superport_v2/test/navigation/navigation_flow_test.dart
2025-10-16 18:53:22 +09:00

361 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:mocktail/mocktail.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/config/environment.dart';
import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/core/permissions/permission_manager.dart';
import 'package:superport_v2/core/theme/superport_shad_theme.dart';
import 'package:superport_v2/core/services/token_storage.dart';
import 'package:superport_v2/features/auth/application/auth_service.dart';
import 'package:superport_v2/features/auth/domain/entities/auth_session.dart';
import 'package:superport_v2/features/auth/domain/entities/authenticated_user.dart';
import 'package:superport_v2/features/auth/domain/entities/login_request.dart';
import 'package:superport_v2/features/auth/domain/repositories/auth_repository.dart';
import 'package:superport_v2/features/login/presentation/pages/login_page.dart';
import 'package:superport_v2/features/masters/group/domain/entities/group.dart';
import 'package:superport_v2/features/masters/group/domain/repositories/group_repository.dart';
import 'package:superport_v2/features/masters/group_permission/domain/entities/group_permission.dart';
import 'package:superport_v2/features/masters/group_permission/domain/repositories/group_permission_repository.dart';
GoRouter _createTestRouter() {
return GoRouter(
initialLocation: loginRoutePath,
routes: [
GoRoute(
path: loginRoutePath,
builder: (context, state) => const LoginPage(),
),
GoRoute(
path: dashboardRoutePath,
builder: (context, state) => const _TestDashboardPage(),
),
GoRoute(
path: '/inventory/inbound',
builder: (context, state) => const _PlaceholderPage(title: '입고 화면'),
),
GoRoute(
path: '/inventory/outbound',
builder: (context, state) => const _PlaceholderPage(title: '출고 화면'),
),
GoRoute(
path: '/inventory/rental',
builder: (context, state) => const _PlaceholderPage(title: '대여 화면'),
),
],
);
}
class _TestApp extends StatelessWidget {
const _TestApp({required this.router});
final GoRouter router;
@override
Widget build(BuildContext context) {
return PermissionScope(
manager: PermissionManager(),
child: ShadApp.router(
routerConfig: router,
debugShowCheckedModeBanner: false,
theme: SuperportShadTheme.light(),
darkTheme: SuperportShadTheme.dark(),
),
);
}
}
class _TestDashboardPage extends StatelessWidget {
const _TestDashboardPage();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('테스트 대시보드'),
actions: [
IconButton(
tooltip: '로그아웃',
icon: const Icon(Icons.logout),
onPressed: () => context.go(loginRoutePath),
),
],
),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextButton(
onPressed: () => context.go('/inventory/inbound'),
child: const Text('입고로 이동'),
),
TextButton(
onPressed: () => context.go('/inventory/outbound'),
child: const Text('출고로 이동'),
),
TextButton(
onPressed: () => context.go('/inventory/rental'),
child: const Text('대여로 이동'),
),
],
),
),
);
}
}
class _PlaceholderPage extends StatelessWidget {
const _PlaceholderPage({required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: TextButton(
onPressed: () => context.go(dashboardRoutePath),
child: const Text('대시보드로 돌아가기'),
),
),
);
}
}
class _StubGroupRepository implements GroupRepository {
@override
Future<Group> create(GroupInput input) {
throw UnimplementedError();
}
@override
Future<void> delete(int id) {
throw UnimplementedError();
}
@override
Future<PaginatedResult<Group>> list({
int page = 1,
int pageSize = 20,
String? query,
bool? isDefault,
bool? isActive,
bool includePermissions = false,
bool includeEmployees = false,
}) async {
return PaginatedResult<Group>(
items: [
Group(
id: 1,
groupName: '기본 그룹',
description: '테스트',
isDefault: true,
isActive: true,
),
],
page: 1,
pageSize: 1,
total: 1,
);
}
@override
Future<Group> restore(int id) {
throw UnimplementedError();
}
@override
Future<Group> update(int id, GroupInput input) {
throw UnimplementedError();
}
}
class _StubGroupPermissionRepository implements GroupPermissionRepository {
@override
Future<GroupPermission> create(GroupPermissionInput input) {
throw UnimplementedError();
}
@override
Future<void> delete(int id) {
throw UnimplementedError();
}
@override
Future<PaginatedResult<GroupPermission>> list({
int page = 1,
int pageSize = 20,
int? groupId,
int? menuId,
bool? isActive,
bool includeDeleted = false,
}) async {
return PaginatedResult<GroupPermission>(
items: [
GroupPermission(
id: 1,
group: GroupPermissionGroup(id: groupId ?? 1, groupName: '기본 그룹'),
menu: GroupPermissionMenu(
id: 10,
menuCode: 'DASHBOARD',
menuName: '대시보드',
path: dashboardRoutePath,
),
canCreate: true,
canRead: true,
canUpdate: true,
canDelete: true,
),
],
page: 1,
pageSize: 1,
total: 1,
);
}
@override
Future<GroupPermission> restore(int id) {
throw UnimplementedError();
}
@override
Future<GroupPermission> update(int id, GroupPermissionInput input) {
throw UnimplementedError();
}
}
class _MockAuthRepository extends Mock implements AuthRepository {}
class _FakeTokenStorage implements TokenStorage {
String? _accessToken;
String? _refreshToken;
@override
Future<void> clear() async {
_accessToken = null;
_refreshToken = null;
}
@override
Future<String?> readAccessToken() async => _accessToken;
@override
Future<String?> readRefreshToken() async => _refreshToken;
@override
Future<void> writeAccessToken(String? token) async {
_accessToken = token;
}
@override
Future<void> writeRefreshToken(String? token) async {
_refreshToken = token;
}
}
AuthSession _buildSampleSession() {
return const AuthSession(
accessToken: 'access-token',
refreshToken: 'refresh-token',
expiresAt: null,
user: AuthenticatedUser(id: 1, name: '테스터'),
permissions: [],
);
}
void _registerAuthService(
_MockAuthRepository repository,
_FakeTokenStorage storage,
) {
final service = AuthService(repository: repository, tokenStorage: storage);
when(() => repository.login(any())).thenAnswer((_) async => _buildSampleSession());
when(() => repository.refresh(any())).thenThrow(UnimplementedError());
GetIt.I.registerSingleton<AuthService>(service);
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
registerFallbackValue(
LoginRequest(identifier: '', password: '', rememberMe: false),
);
});
late _MockAuthRepository authRepository;
late _FakeTokenStorage tokenStorage;
setUp(() async {
await GetIt.I.reset();
authRepository = _MockAuthRepository();
tokenStorage = _FakeTokenStorage();
_registerAuthService(authRepository, tokenStorage);
});
setUpAll(() async {
await Environment.initialize();
});
Finder editableTextAt(int index) => find.byType(EditableText).at(index);
testWidgets('사용자가 로그인 후 주요 화면을 탐색할 수 있다', (tester) async {
final view = tester.view;
view.physicalSize = const Size(1080, 720);
view.devicePixelRatio = 1.0;
addTearDown(() {
view.resetPhysicalSize();
view.resetDevicePixelRatio();
});
GetIt.I.registerSingleton<GroupRepository>(_StubGroupRepository());
GetIt.I.registerSingleton<GroupPermissionRepository>(
_StubGroupPermissionRepository(),
);
final router = _createTestRouter();
await tester.pumpWidget(_TestApp(router: router));
await tester.pumpAndSettle();
expect(find.text('Superport v2 로그인'), findsOneWidget);
await tester.enterText(editableTextAt(0), 'tester');
await tester.enterText(editableTextAt(1), 'password123');
await tester.tap(find.widgetWithText(ShadButton, '로그인'));
await tester.pump(const Duration(milliseconds: 650));
await tester.pumpAndSettle();
expect(find.text('테스트 대시보드'), findsOneWidget);
await tester.tap(find.widgetWithText(TextButton, '입고로 이동'));
await tester.pumpAndSettle();
expect(find.text('입고 화면'), findsOneWidget);
await tester.tap(find.widgetWithText(TextButton, '대시보드로 돌아가기'));
await tester.pumpAndSettle();
expect(find.text('테스트 대시보드'), findsOneWidget);
await tester.tap(find.widgetWithText(TextButton, '출고로 이동'));
await tester.pumpAndSettle();
expect(find.text('출고 화면'), findsOneWidget);
await tester.tap(find.widgetWithText(TextButton, '대시보드로 돌아가기'));
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(TextButton, '대여로 이동'));
await tester.pumpAndSettle();
expect(find.text('대여 화면'), findsOneWidget);
await tester.tap(find.widgetWithText(TextButton, '대시보드로 돌아가기'));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('로그아웃'));
await tester.pumpAndSettle();
expect(find.text('Superport v2 로그인'), findsOneWidget);
});
}