결재 단계 편집 다이얼로그 구현

This commit is contained in:
JiWoong Sul
2025-09-25 17:57:29 +09:00
parent 6d6781f552
commit 8a6ad1e81b
17 changed files with 1689 additions and 42 deletions

View File

@@ -0,0 +1,134 @@
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/history/domain/entities/approval_history_record.dart';
import 'package:superport_v2/features/approvals/history/domain/repositories/approval_history_repository.dart';
import 'package:superport_v2/features/approvals/history/presentation/controllers/approval_history_controller.dart';
class _MockApprovalHistoryRepository extends Mock
implements ApprovalHistoryRepository {}
void main() {
late ApprovalHistoryController controller;
late _MockApprovalHistoryRepository repository;
final record = ApprovalHistoryRecord(
id: 1,
approvalId: 10,
approvalNo: 'APP-2024-0001',
stepOrder: 1,
action: ApprovalAction(id: 11, name: 'approve'),
fromStatus: ApprovalStatus(id: 1, name: '대기', color: null),
toStatus: ApprovalStatus(id: 2, name: '승인', color: null),
approver: ApprovalApprover(id: 21, employeeNo: 'E001', name: '최승인'),
actionAt: DateTime(2024, 4, 1, 9, 30),
note: '승인 완료',
);
PaginatedResult<ApprovalHistoryRecord> createResult(
List<ApprovalHistoryRecord> items,
) {
return PaginatedResult<ApprovalHistoryRecord>(
items: items,
page: 1,
pageSize: 20,
total: items.length,
);
}
setUp(() {
repository = _MockApprovalHistoryRepository();
controller = ApprovalHistoryController(repository: repository);
});
test('fetch 성공 시 결과를 갱신한다', () async {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
action: any(named: 'action'),
from: any(named: 'from'),
to: any(named: 'to'),
),
).thenAnswer((_) async => createResult([record]));
await controller.fetch();
expect(controller.result?.items, isNotEmpty);
expect(controller.errorMessage, isNull);
verify(
() => repository.list(
page: 1,
pageSize: 20,
query: null,
action: null,
from: null,
to: null,
),
).called(1);
});
test('필터 적용 시 파라미터를 전달한다', () async {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
action: any(named: 'action'),
from: any(named: 'from'),
to: any(named: 'to'),
),
).thenAnswer((_) async => createResult([record]));
controller.updateQuery('APP');
controller.updateActionFilter(ApprovalHistoryActionFilter.approve);
controller.updateDateRange(DateTime(2024, 4, 1), DateTime(2024, 4, 30));
await controller.fetch(page: 2);
verify(
() => repository.list(
page: 2,
pageSize: 20,
query: 'APP',
action: 'approve',
from: DateTime(2024, 4, 1),
to: DateTime(2024, 4, 30),
),
).called(1);
});
test('에러 발생 시 errorMessage에 저장한다', () async {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
action: any(named: 'action'),
from: any(named: 'from'),
to: any(named: 'to'),
),
).thenThrow(Exception('fail'));
await controller.fetch();
expect(controller.errorMessage, isNotNull);
expect(controller.result, isNull);
});
test('clearFilters가 상태를 초기화한다', () {
controller.updateQuery('APP');
controller.updateActionFilter(ApprovalHistoryActionFilter.comment);
controller.updateDateRange(DateTime(2024, 4, 1), DateTime(2024, 4, 5));
controller.clearFilters();
expect(controller.query, isEmpty);
expect(controller.actionFilter, ApprovalHistoryActionFilter.all);
expect(controller.from, isNull);
expect(controller.to, isNull);
});
}

View File

@@ -0,0 +1,107 @@
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:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
import 'package:superport_v2/features/approvals/history/domain/entities/approval_history_record.dart';
import 'package:superport_v2/features/approvals/history/domain/repositories/approval_history_repository.dart';
import 'package:superport_v2/features/approvals/history/presentation/pages/approval_history_page.dart';
class _MockApprovalHistoryRepository extends Mock
implements ApprovalHistoryRepository {}
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 _MockApprovalHistoryRepository repository;
final record = ApprovalHistoryRecord(
id: 1,
approvalId: 10,
approvalNo: 'APP-2024-0001',
stepOrder: 1,
action: ApprovalAction(id: 11, name: 'approve'),
fromStatus: ApprovalStatus(id: 1, name: '대기', color: null),
toStatus: ApprovalStatus(id: 2, name: '승인', color: null),
approver: ApprovalApprover(id: 21, employeeNo: 'E001', name: '최승인'),
actionAt: DateTime(2024, 4, 1, 9, 30),
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 ApprovalHistoryPage()));
await tester.pump();
expect(find.text('결재 이력 조회'), findsOneWidget);
expect(find.text('결재 단계별 변경 이력을 조회합니다.'), findsOneWidget);
});
testWidgets('이력 목록을 렌더링하고 검색 필터를 적용한다', (tester) async {
dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');
repository = _MockApprovalHistoryRepository();
GetIt.I.registerLazySingleton<ApprovalHistoryRepository>(() => repository);
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
action: any(named: 'action'),
from: any(named: 'from'),
to: any(named: 'to'),
),
).thenAnswer(
(_) async => PaginatedResult<ApprovalHistoryRecord>(
items: [record],
page: 1,
pageSize: 20,
total: 1,
),
);
await tester.pumpWidget(_buildApp(const ApprovalHistoryPage()));
await tester.pump();
await tester.pumpAndSettle();
expect(find.textContaining('APP-2024-0001'), findsOneWidget);
expect(find.text('승인 완료'), findsOneWidget);
await tester.enterText(find.byType(ShadInput).first, 'APP-2024');
await tester.tap(find.text('검색 적용'));
await tester.pump();
verify(
() => repository.list(
page: any(named: 'page'),
pageSize: 20,
query: 'APP-2024',
action: null,
from: null,
to: null,
),
).called(greaterThanOrEqualTo(1));
});
}