전역 구조 리팩터링 및 테스트 확장
This commit is contained in:
73
test/core/permissions/permission_manager_test.dart
Normal file
73
test/core/permissions/permission_manager_test.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:superport_v2/core/permissions/permission_manager.dart';
|
||||
|
||||
void main() {
|
||||
group('PermissionManager', () {
|
||||
test('falls back to environment permissions when no override', () {
|
||||
final manager = PermissionManager();
|
||||
final allowed = manager.can('/any', PermissionAction.view);
|
||||
expect(allowed, isTrue);
|
||||
});
|
||||
|
||||
test('respects overrides', () {
|
||||
final manager = PermissionManager(
|
||||
overrides: {
|
||||
'/inventory/inbound': {
|
||||
PermissionAction.view,
|
||||
PermissionAction.create,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(manager.can('/inventory/inbound', PermissionAction.view), isTrue);
|
||||
expect(
|
||||
manager.can('/inventory/inbound', PermissionAction.create),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
manager.can('/inventory/inbound', PermissionAction.delete),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('PermissionGate hides child when unauthorized', (tester) async {
|
||||
final manager = PermissionManager(overrides: {'/resource': {}});
|
||||
await tester.pumpWidget(
|
||||
PermissionScope(
|
||||
manager: manager,
|
||||
child: const Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: PermissionGate(
|
||||
resource: '/resource',
|
||||
action: PermissionAction.view,
|
||||
child: Text('secret'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('secret'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('PermissionGate shows fallback when provided', (tester) async {
|
||||
final manager = PermissionManager(overrides: {'/resource': {}});
|
||||
await tester.pumpWidget(
|
||||
PermissionScope(
|
||||
manager: manager,
|
||||
child: const Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: PermissionGate(
|
||||
resource: '/resource',
|
||||
action: PermissionAction.view,
|
||||
fallback: Text('fallback'),
|
||||
child: Text('secret'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('fallback'), findsOneWidget);
|
||||
expect(find.text('secret'), findsNothing);
|
||||
});
|
||||
}
|
||||
@@ -55,6 +55,5 @@ void main() {
|
||||
repository = _MockApprovalRepository();
|
||||
GetIt.I.registerLazySingleton<ApprovalRepository>(() => repository);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -138,6 +138,48 @@ void main() {
|
||||
expect(find.text('고객사명을 입력하세요.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('우편번호를 수동 입력하면 검색 안내를 노출한다', (tester) async {
|
||||
when(
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
isPartner: any(named: 'isPartner'),
|
||||
isGeneral: any(named: 'isGeneral'),
|
||||
isActive: any(named: 'isActive'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => PaginatedResult<Customer>(
|
||||
items: const [],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildApp(CustomerPage(routeUri: Uri(path: '/masters/customers'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('신규 등록'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final fields = find.descendant(
|
||||
of: find.byType(Dialog),
|
||||
matching: find.byType(EditableText),
|
||||
);
|
||||
|
||||
await tester.enterText(fields.at(0), 'C-200');
|
||||
await tester.enterText(fields.at(1), '검색 필요 고객');
|
||||
await tester.enterText(fields.at(4), '06000');
|
||||
|
||||
await tester.tap(find.text('등록'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('우편번호 검색으로 주소를 선택하세요.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('신규 등록 성공 시 repository.create 호출', (tester) async {
|
||||
var listCallCount = 0;
|
||||
when(
|
||||
|
||||
@@ -139,7 +139,7 @@ void main() {
|
||||
await tester.pumpWidget(_buildApp(const GroupPermissionPage()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('대시보드'), findsOneWidget);
|
||||
expect(find.text('대시보드'), findsWidgets);
|
||||
expect(find.text('관리자'), findsOneWidget);
|
||||
});
|
||||
|
||||
@@ -278,7 +278,7 @@ void main() {
|
||||
expect(capturedInput?.canCreate, isTrue);
|
||||
expect(capturedInput?.canUpdate, isTrue);
|
||||
expect(find.byType(Dialog), findsNothing);
|
||||
expect(find.text('대시보드'), findsOneWidget);
|
||||
expect(find.text('대시보드'), findsWidgets);
|
||||
verify(() => permissionRepository.create(any())).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,7 +49,9 @@ void main() {
|
||||
testWidgets('플래그 Off 시 스펙 문서 화면을 노출한다', (tester) async {
|
||||
dotenv.testLoad(fileInput: 'FEATURE_PRODUCTS_ENABLED=false\n');
|
||||
|
||||
await tester.pumpWidget(_buildApp(const ProductPage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(ProductPage(routeUri: Uri(path: '/masters/products'))),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('장비 모델(제품) 관리'), findsOneWidget);
|
||||
@@ -134,7 +136,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const ProductPage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(ProductPage(routeUri: Uri(path: '/masters/products'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('P-001'), findsOneWidget);
|
||||
@@ -169,7 +173,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const ProductPage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(ProductPage(routeUri: Uri(path: '/masters/products'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('신규 등록'));
|
||||
@@ -243,7 +249,9 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(_buildApp(const ProductPage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(ProductPage(routeUri: Uri(path: '/masters/products'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('신규 등록'));
|
||||
|
||||
@@ -55,7 +55,6 @@ void main() {
|
||||
group('플래그 On', () {
|
||||
late _MockUserRepository userRepository;
|
||||
late _MockGroupRepository groupRepository;
|
||||
|
||||
setUp(() {
|
||||
dotenv.testLoad(fileInput: 'FEATURE_USERS_ENABLED=true\n');
|
||||
userRepository = _MockUserRepository();
|
||||
@@ -153,6 +152,14 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('신규 등록 성공', (tester) async {
|
||||
final view = tester.view;
|
||||
view.physicalSize = const Size(1280, 800);
|
||||
view.devicePixelRatio = 1.0;
|
||||
addTearDown(() {
|
||||
view.resetPhysicalSize();
|
||||
view.resetDevicePixelRatio();
|
||||
});
|
||||
|
||||
var listCallCount = 0;
|
||||
when(
|
||||
() => userRepository.list(
|
||||
@@ -214,9 +221,21 @@ void main() {
|
||||
await tester.enterText(editableTexts.at(0), 'A010');
|
||||
await tester.enterText(editableTexts.at(1), '신규 사용자');
|
||||
|
||||
await tester.tap(find.text('그룹을 선택하세요'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('관리자'));
|
||||
final selectFinder = find.descendant(
|
||||
of: dialog,
|
||||
matching: find.byType(ShadSelect<int?>),
|
||||
);
|
||||
final selectElement = tester.element(selectFinder);
|
||||
final renderBox = selectElement.renderObject as RenderBox;
|
||||
final globalCenter = renderBox.localToGlobal(
|
||||
renderBox.size.center(Offset.zero),
|
||||
);
|
||||
await tester.tapAt(globalCenter);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
final adminOption = find.text('관리자', skipOffstage: false);
|
||||
expect(adminOption, findsWidgets);
|
||||
await tester.tap(adminOption.first, warnIfMissed: false);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('등록'));
|
||||
|
||||
@@ -41,7 +41,9 @@ void main() {
|
||||
testWidgets('FEATURE_VENDORS_ENABLED=false 이면 스펙 페이지를 노출한다', (tester) async {
|
||||
dotenv.testLoad(fileInput: 'FEATURE_VENDORS_ENABLED=false\n');
|
||||
|
||||
await tester.pumpWidget(_buildApp(const VendorPage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(VendorPage(routeUri: Uri(path: '/masters/vendors'))),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('제조사(벤더) 관리'), findsOneWidget);
|
||||
@@ -71,7 +73,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const VendorPage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(VendorPage(routeUri: Uri(path: '/masters/vendors'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('V-001'), findsOneWidget);
|
||||
@@ -101,7 +105,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const VendorPage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(VendorPage(routeUri: Uri(path: '/masters/vendors'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('신규 등록'));
|
||||
@@ -155,7 +161,9 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(_buildApp(const VendorPage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(VendorPage(routeUri: Uri(path: '/masters/vendors'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('신규 등록'));
|
||||
|
||||
@@ -9,11 +9,16 @@ import 'package:superport_v2/core/common/models/paginated_result.dart';
|
||||
import 'package:superport_v2/features/masters/warehouse/domain/entities/warehouse.dart';
|
||||
import 'package:superport_v2/features/masters/warehouse/domain/repositories/warehouse_repository.dart';
|
||||
import 'package:superport_v2/features/masters/warehouse/presentation/pages/warehouse_page.dart';
|
||||
import 'package:superport_v2/features/util/postal_search/domain/entities/postal_code.dart';
|
||||
import 'package:superport_v2/features/util/postal_search/domain/repositories/postal_search_repository.dart';
|
||||
|
||||
class _MockWarehouseRepository extends Mock implements WarehouseRepository {}
|
||||
|
||||
class _FakeWarehouseInput extends Fake implements WarehouseInput {}
|
||||
|
||||
class _MockPostalSearchRepository extends Mock
|
||||
implements PostalSearchRepository {}
|
||||
|
||||
Widget _buildApp(Widget child) {
|
||||
return MaterialApp(
|
||||
home: ShadTheme(
|
||||
@@ -41,7 +46,9 @@ void main() {
|
||||
testWidgets('플래그 Off 시 스펙 화면', (tester) async {
|
||||
dotenv.testLoad(fileInput: 'FEATURE_WAREHOUSES_ENABLED=false\n');
|
||||
|
||||
await tester.pumpWidget(_buildApp(const WarehousePage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(WarehousePage(routeUri: Uri(path: '/masters/warehouses'))),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('입고지(창고) 관리'), findsOneWidget);
|
||||
@@ -50,11 +57,32 @@ void main() {
|
||||
|
||||
group('플래그 On', () {
|
||||
late _MockWarehouseRepository repository;
|
||||
late _MockPostalSearchRepository postalRepository;
|
||||
|
||||
setUp(() {
|
||||
dotenv.testLoad(fileInput: 'FEATURE_WAREHOUSES_ENABLED=true\n');
|
||||
repository = _MockWarehouseRepository();
|
||||
postalRepository = _MockPostalSearchRepository();
|
||||
GetIt.I.registerLazySingleton<WarehouseRepository>(() => repository);
|
||||
GetIt.I.registerLazySingleton<PostalSearchRepository>(
|
||||
() => postalRepository,
|
||||
);
|
||||
when(
|
||||
() => postalRepository.search(
|
||||
keyword: any(named: 'keyword'),
|
||||
limit: any(named: 'limit'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => [
|
||||
PostalCode(
|
||||
zipcode: '06000',
|
||||
sido: '서울특별시',
|
||||
sigungu: '강남구',
|
||||
roadName: '테헤란로',
|
||||
buildingMainNo: 100,
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('목록 조회 후 테이블 표시', (tester) async {
|
||||
@@ -81,7 +109,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const WarehousePage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(WarehousePage(routeUri: Uri(path: '/masters/warehouses'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('WH-001'), findsOneWidget);
|
||||
@@ -108,7 +138,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const WarehousePage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(WarehousePage(routeUri: Uri(path: '/masters/warehouses'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('신규 등록'));
|
||||
@@ -164,7 +196,9 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(_buildApp(const WarehousePage()));
|
||||
await tester.pumpWidget(
|
||||
_buildApp(WarehousePage(routeUri: Uri(path: '/masters/warehouses'))),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('신규 등록'));
|
||||
@@ -177,14 +211,31 @@ void main() {
|
||||
|
||||
await tester.enterText(fields.at(0), 'WH-100');
|
||||
await tester.enterText(fields.at(1), '신규 창고');
|
||||
await tester.enterText(fields.at(2), '12345');
|
||||
await tester.enterText(fields.at(3), '주소');
|
||||
await tester.enterText(fields.at(2), '06000');
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(Dialog),
|
||||
matching: find.widgetWithText(ShadButton, '검색'),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('06000').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final updatedFields = find.descendant(
|
||||
of: find.byType(Dialog),
|
||||
matching: find.byType(EditableText),
|
||||
);
|
||||
|
||||
await tester.enterText(updatedFields.at(3), '주소');
|
||||
|
||||
await tester.tap(find.text('등록'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(capturedInput, isNotNull);
|
||||
expect(capturedInput?.warehouseCode, 'WH-100');
|
||||
expect(capturedInput?.zipcode, '06000');
|
||||
expect(find.byType(Dialog), findsNothing);
|
||||
expect(find.text('WH-100'), findsOneWidget);
|
||||
verify(() => repository.create(any())).called(1);
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import 'package:superport_v2/features/util/postal_search/presentation/models/postal_search_result.dart';
|
||||
import 'package:superport_v2/features/util/postal_search/presentation/widgets/postal_search_dialog.dart';
|
||||
|
||||
class _PostalSearchHarness extends StatefulWidget {
|
||||
const _PostalSearchHarness({required this.fetcher, this.initialKeyword});
|
||||
|
||||
final PostalSearchFetcher fetcher;
|
||||
final String? initialKeyword;
|
||||
|
||||
@override
|
||||
State<_PostalSearchHarness> createState() => _PostalSearchHarnessState();
|
||||
}
|
||||
|
||||
class _PostalSearchHarnessState extends State<_PostalSearchHarness> {
|
||||
PostalSearchResult? _selection;
|
||||
bool _dialogScheduled = false;
|
||||
|
||||
void _ensureDialogShown(BuildContext innerContext) {
|
||||
if (_dialogScheduled) {
|
||||
return;
|
||||
}
|
||||
_dialogScheduled = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final result = await showPostalSearchDialog(
|
||||
innerContext,
|
||||
fetcher: widget.fetcher,
|
||||
initialKeyword: widget.initialKeyword,
|
||||
);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_selection = result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Builder(
|
||||
builder: (innerContext) {
|
||||
return ShadTheme(
|
||||
data: ShadThemeData(
|
||||
colorScheme: const ShadSlateColorScheme.light(),
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
child: Builder(
|
||||
builder: (themeContext) {
|
||||
_ensureDialogShown(themeContext);
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
_selection?.zipcode ?? '선택 없음',
|
||||
key: const Key('selected_zipcode'),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('검색 전 안내 문구를 노출하고 fetcher를 호출하지 않는다', (tester) async {
|
||||
var called = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
_PostalSearchHarness(
|
||||
fetcher: (keyword) async {
|
||||
called = true;
|
||||
return [];
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('검색어를 입력한 뒤 엔터 또는 검색 버튼을 눌러 주세요.'), findsOneWidget);
|
||||
expect(called, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('검색 실행 후 결과를 선택하면 선택 정보가 반환된다', (tester) async {
|
||||
var receivedKeyword = '';
|
||||
|
||||
await tester.pumpWidget(
|
||||
_PostalSearchHarness(
|
||||
fetcher: (keyword) async {
|
||||
receivedKeyword = keyword;
|
||||
return [
|
||||
PostalSearchResult(
|
||||
zipcode: '06000',
|
||||
sido: '서울특별시',
|
||||
sigungu: '강남구',
|
||||
roadName: '언주로',
|
||||
buildingNumber: '100',
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final inputFinder = find.byType(EditableText);
|
||||
expect(inputFinder, findsOneWidget);
|
||||
|
||||
await tester.enterText(inputFinder, '언주로');
|
||||
await tester.tap(find.text('검색'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(receivedKeyword, '언주로');
|
||||
expect(find.text('검색 결과 1건'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('06000'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final selectedFinder = find.byKey(const Key('selected_zipcode'));
|
||||
expect(selectedFinder, findsOneWidget);
|
||||
final selectedText = tester.widget<Text>(selectedFinder);
|
||||
expect(selectedText.data, '06000');
|
||||
});
|
||||
|
||||
testWidgets('initialKeyword가 주어지면 자동으로 검색을 수행한다', (tester) async {
|
||||
String? receivedKeyword;
|
||||
|
||||
await tester.pumpWidget(
|
||||
_PostalSearchHarness(
|
||||
fetcher: (keyword) async {
|
||||
receivedKeyword = keyword;
|
||||
return [];
|
||||
},
|
||||
initialKeyword: '06236',
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(receivedKeyword, '06236');
|
||||
|
||||
await tester.tap(find.text('닫기'));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
}
|
||||
17
test/helpers/test_app.dart
Normal file
17
test/helpers/test_app.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import 'package:superport_v2/core/permissions/permission_manager.dart';
|
||||
import 'package:superport_v2/core/theme/superport_shad_theme.dart';
|
||||
|
||||
Widget buildTestApp(Widget child, {PermissionManager? permissionManager}) {
|
||||
return PermissionScope(
|
||||
manager: permissionManager ?? PermissionManager(),
|
||||
child: ShadApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: SuperportShadTheme.light(),
|
||||
darkTheme: SuperportShadTheme.dark(),
|
||||
home: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
175
test/navigation/navigation_flow_test.dart
Normal file
175
test/navigation/navigation_flow_test.dart
Normal file
@@ -0,0 +1,175 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:superport_v2/core/config/environment.dart';
|
||||
import 'package:superport_v2/core/constants/app_sections.dart';
|
||||
import 'package:superport_v2/features/login/presentation/pages/login_page.dart';
|
||||
import 'package:superport_v2/core/theme/superport_shad_theme.dart';
|
||||
import 'package:superport_v2/core/permissions/permission_manager.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('대시보드로 돌아가기'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
54
test/widgets/dialog_keyboard_shortcuts_test.dart
Normal file
54
test/widgets/dialog_keyboard_shortcuts_test.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport_v2/widgets/components/keyboard_shortcuts.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('DialogKeyboardShortcuts handles escape and enter', (
|
||||
tester,
|
||||
) async {
|
||||
var escape = 0;
|
||||
var submit = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: DialogKeyboardShortcuts(
|
||||
onEscape: () => escape++,
|
||||
onSubmit: () => submit++,
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pump();
|
||||
expect(escape, 1);
|
||||
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pump();
|
||||
expect(submit, 1);
|
||||
});
|
||||
|
||||
testWidgets('DialogKeyboardShortcuts does not submit from multiline input', (
|
||||
tester,
|
||||
) async {
|
||||
var submit = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: DialogKeyboardShortcuts(
|
||||
onSubmit: () => submit++,
|
||||
child: const Material(child: TextField(maxLines: 5)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump();
|
||||
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pump();
|
||||
|
||||
expect(submit, 0);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user