결재 단계 목록 화면과 테스트 도입
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import 'package:superport_v2/core/common/models/paginated_result.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
|
||||
import 'package:superport_v2/features/approvals/step/domain/entities/approval_step_record.dart';
|
||||
import 'package:superport_v2/features/approvals/step/domain/repositories/approval_step_repository.dart';
|
||||
import 'package:superport_v2/features/approvals/step/presentation/controllers/approval_step_controller.dart';
|
||||
|
||||
class _MockApprovalStepRepository extends Mock
|
||||
implements ApprovalStepRepository {}
|
||||
|
||||
void main() {
|
||||
late ApprovalStepController controller;
|
||||
late _MockApprovalStepRepository repository;
|
||||
|
||||
final sampleRecord = ApprovalStepRecord(
|
||||
approvalId: 10,
|
||||
approvalNo: 'APP-2024-0001',
|
||||
transactionNo: 'TRX-2024-01',
|
||||
templateName: '입고 기본',
|
||||
step: ApprovalStep(
|
||||
id: 100,
|
||||
stepOrder: 1,
|
||||
approver: ApprovalApprover(id: 21, employeeNo: 'E001', name: '최승인'),
|
||||
status: ApprovalStatus(id: 1, name: '승인대기', color: null),
|
||||
assignedAt: DateTime(2024, 4, 1, 9),
|
||||
decidedAt: null,
|
||||
note: '확인 요청',
|
||||
),
|
||||
);
|
||||
|
||||
PaginatedResult<ApprovalStepRecord> createResult(
|
||||
List<ApprovalStepRecord> items,
|
||||
) {
|
||||
return PaginatedResult<ApprovalStepRecord>(
|
||||
items: items,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: items.length,
|
||||
);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
repository = _MockApprovalStepRepository();
|
||||
controller = ApprovalStepController(repository: repository);
|
||||
});
|
||||
|
||||
test('fetch 성공 시 결과를 갱신한다', () async {
|
||||
when(
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
statusId: any(named: 'statusId'),
|
||||
approverId: any(named: 'approverId'),
|
||||
approvalId: any(named: 'approvalId'),
|
||||
),
|
||||
).thenAnswer((_) async => createResult([sampleRecord]));
|
||||
|
||||
await controller.fetch();
|
||||
|
||||
expect(controller.result?.items, isNotEmpty);
|
||||
expect(controller.errorMessage, isNull);
|
||||
verify(
|
||||
() => repository.list(
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
query: null,
|
||||
statusId: null,
|
||||
approverId: null,
|
||||
approvalId: null,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('에러 발생 시 errorMessage를 설정한다', () async {
|
||||
when(
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
statusId: any(named: 'statusId'),
|
||||
approverId: any(named: 'approverId'),
|
||||
approvalId: any(named: 'approvalId'),
|
||||
),
|
||||
).thenThrow(Exception('fail'));
|
||||
|
||||
await controller.fetch();
|
||||
|
||||
expect(controller.errorMessage, isNotNull);
|
||||
expect(controller.result, isNull);
|
||||
});
|
||||
|
||||
test('필터 갱신 후 fetch 시 파라미터에 반영한다', () async {
|
||||
when(
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
statusId: any(named: 'statusId'),
|
||||
approverId: any(named: 'approverId'),
|
||||
approvalId: any(named: 'approvalId'),
|
||||
),
|
||||
).thenAnswer((_) async => createResult([sampleRecord]));
|
||||
|
||||
controller.updateQuery('APP-2024');
|
||||
controller.updateStatusId(2);
|
||||
|
||||
await controller.fetch(page: 3);
|
||||
|
||||
verify(
|
||||
() => repository.list(
|
||||
page: 3,
|
||||
pageSize: 20,
|
||||
query: 'APP-2024',
|
||||
statusId: 2,
|
||||
approverId: null,
|
||||
approvalId: null,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('fetchDetail 성공 시 selected가 설정된다', () async {
|
||||
when(
|
||||
() => repository.fetchDetail(any()),
|
||||
).thenAnswer((_) async => sampleRecord);
|
||||
|
||||
final detail = await controller.fetchDetail(100);
|
||||
|
||||
expect(detail, isNotNull);
|
||||
expect(controller.selected, isNotNull);
|
||||
verify(() => repository.fetchDetail(100)).called(1);
|
||||
});
|
||||
|
||||
test('fetchDetail 실패 시 null을 반환한다', () async {
|
||||
when(() => repository.fetchDetail(any())).thenThrow(Exception('fail'));
|
||||
|
||||
final detail = await controller.fetchDetail(100);
|
||||
|
||||
expect(detail, isNull);
|
||||
expect(controller.errorMessage, isNotNull);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart';
|
||||
|
||||
import 'package:superport_v2/core/common/models/paginated_result.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
|
||||
import 'package:superport_v2/features/approvals/step/domain/entities/approval_step_record.dart';
|
||||
import 'package:superport_v2/features/approvals/step/domain/repositories/approval_step_repository.dart';
|
||||
import 'package:superport_v2/features/approvals/step/presentation/pages/approval_step_page.dart';
|
||||
|
||||
class _MockApprovalStepRepository extends Mock
|
||||
implements ApprovalStepRepository {}
|
||||
|
||||
Widget _buildApp(Widget child) {
|
||||
return MaterialApp(
|
||||
home: ShadTheme(
|
||||
data: ShadThemeData(
|
||||
colorScheme: const ShadSlateColorScheme.light(),
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
late _MockApprovalStepRepository repository;
|
||||
|
||||
final record = ApprovalStepRecord(
|
||||
approvalId: 10,
|
||||
approvalNo: 'APP-2024-0001',
|
||||
transactionNo: 'TRX-2024-001',
|
||||
templateName: '입고 기본',
|
||||
step: ApprovalStep(
|
||||
id: 501,
|
||||
stepOrder: 1,
|
||||
approver: ApprovalApprover(id: 21, employeeNo: 'E001', name: '최승인'),
|
||||
status: ApprovalStatus(id: 1, name: '승인대기', color: null),
|
||||
assignedAt: DateTime(2024, 4, 1, 9),
|
||||
decidedAt: null,
|
||||
note: '검토 필요',
|
||||
),
|
||||
);
|
||||
|
||||
tearDown(() async {
|
||||
await GetIt.I.reset();
|
||||
dotenv.clean();
|
||||
});
|
||||
|
||||
testWidgets('플래그 Off 시 스펙 페이지를 노출한다', (tester) async {
|
||||
dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=false\n');
|
||||
|
||||
await tester.pumpWidget(_buildApp(const ApprovalStepPage()));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('결재 단계 관리'), findsOneWidget);
|
||||
expect(find.text('결재 단계 순서와 승인자를 구성합니다.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('목록을 렌더링하고 상세 다이얼로그를 연다', (tester) async {
|
||||
dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');
|
||||
repository = _MockApprovalStepRepository();
|
||||
GetIt.I.registerLazySingleton<ApprovalStepRepository>(() => repository);
|
||||
|
||||
when(
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
statusId: any(named: 'statusId'),
|
||||
approverId: any(named: 'approverId'),
|
||||
approvalId: any(named: 'approvalId'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => PaginatedResult<ApprovalStepRecord>(
|
||||
items: [record],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 1,
|
||||
),
|
||||
);
|
||||
|
||||
when(() => repository.fetchDetail(any())).thenAnswer((_) async => record);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const ApprovalStepPage()));
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('APP-2024-0001'), findsOneWidget);
|
||||
expect(find.text('최승인'), findsOneWidget);
|
||||
|
||||
await tester.dragUntilVisible(
|
||||
find.widgetWithText(ShadButton, '상세'),
|
||||
find.byType(TwoDimensionalScrollable),
|
||||
const Offset(-200, 0),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.widgetWithText(ShadButton, '상세').first);
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('결재 단계 상세'), findsOneWidget);
|
||||
expect(find.text('검토 필요'), findsOneWidget);
|
||||
verify(() => repository.fetchDetail(501)).called(1);
|
||||
|
||||
await tester.tap(find.text('닫기'));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user