appsok/test/builds_page_test.dart
toki 74d1f40465 feat(artifact-flow): Jenkins job 선택 흐름을 추가한다
사용자가 Jenkins job 목록을 먼저 확인하고 선택한 뒤 build 목록 단계로 진입할 수 있도록 기본 조회 UI와 클라이언트 모델을 추가한다.
2026-06-09 18:14:09 +09:00

132 lines
3.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:appsok/src/features/builds/builds_page.dart';
import 'package:appsok/src/models/jenkins_build.dart';
import 'package:appsok/src/services/jenkins_client.dart';
import 'package:appsok/src/theme/app_theme.dart';
Widget _wrap(Widget child) => MaterialApp(
theme: AppTheme.light(),
home: Scaffold(body: child),
);
List<JenkinsJob> _sampleJobs() => [
JenkinsJob(
name: 'android-app',
fullName: 'team/android-app',
url: Uri.parse('https://jenkins.example/job/android-app/'),
),
JenkinsJob(
name: 'ios-build',
url: Uri.parse('https://jenkins.example/job/ios-build/'),
),
];
void main() {
testWidgets('renders login required state without job loader', (
tester,
) async {
await tester.pumpWidget(_wrap(const BuildsPage()));
expect(find.text('Jenkins 연결 필요'), findsOneWidget);
});
testWidgets('renders searchable Jenkins jobs', (tester) async {
await tester.pumpWidget(
_wrap(BuildsPage(jobLoader: () async => _sampleJobs())),
);
await tester.pumpAndSettle();
expect(find.text('android-app'), findsOneWidget);
expect(find.text('ios-build'), findsOneWidget);
expect(find.byKey(const ValueKey('job-search-field')), findsOneWidget);
});
testWidgets('filters jobs by name full name and url', (tester) async {
await tester.pumpWidget(
_wrap(BuildsPage(jobLoader: () async => _sampleJobs())),
);
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const ValueKey('job-search-field')),
'android',
);
await tester.pump();
expect(find.text('android-app'), findsOneWidget);
expect(find.text('ios-build'), findsNothing);
});
testWidgets('selects a job and shows build list placeholder', (tester) async {
await tester.pumpWidget(
_wrap(BuildsPage(jobLoader: () async => _sampleJobs())),
);
await tester.pumpAndSettle();
await tester.tap(find.text('android-app'));
await tester.pumpAndSettle();
expect(
find.byKey(const ValueKey('build-list-placeholder')),
findsOneWidget,
);
});
testWidgets('shows empty state when no jobs are returned', (tester) async {
await tester.pumpWidget(_wrap(BuildsPage(jobLoader: () async => [])));
await tester.pumpAndSettle();
expect(find.text('접근 가능한 job 없음'), findsOneWidget);
});
testWidgets('shows permission state for forbidden Jenkins response', (
tester,
) async {
await tester.pumpWidget(
_wrap(
BuildsPage(
jobLoader: () async => throw const JenkinsClientException(
statusCode: 403,
message: 'Forbidden',
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('권한 없음'), findsOneWidget);
});
testWidgets('shows failure state for network error', (tester) async {
await tester.pumpWidget(
_wrap(
BuildsPage(
jobLoader: () async => throw const JenkinsClientException(
statusCode: 500,
message: 'Error',
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Jenkins 조회 실패'), findsOneWidget);
});
testWidgets('renders job selector without overflow at compact viewport', (
tester,
) async {
tester.view.physicalSize = const Size(600, 400);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.reset);
await tester.pumpWidget(
_wrap(BuildsPage(jobLoader: () async => _sampleJobs())),
);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
}