323 lines
13 KiB
Dart
323 lines
13 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:fancake/app.dart';
|
|
import 'package:fancake/core/data/repositories.dart';
|
|
import 'package:fancake/core/firebase/firebase_boot.dart';
|
|
import 'package:fancake/core/i18n/app_locale.dart';
|
|
import 'package:fancake/features/planet/planet_data.dart' show countryName;
|
|
import 'package:fancake/features/auth/auth.dart';
|
|
import 'package:fancake/features/auth/auth_repository.dart';
|
|
import 'package:fancake/features/auth/login_screen.dart';
|
|
import 'package:fancake/features/auth/onboarding_data.dart';
|
|
import 'package:fancake/features/auth/onboarding_screens.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
/// 온보딩 6단계(10/3 통합회신 1항) · 「로그인」 버튼 신규 계정의 약관 우회 차단(E-05).
|
|
void main() {
|
|
setUp(() {
|
|
FirebaseBoot.debugSetMode(FirebaseMode.mock, reason: '온보딩 테스트');
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
Future<(AuthRepository, SampleOnboardingRepository)> boot(
|
|
WidgetTester tester, {
|
|
required bool needs,
|
|
bool intro = false,
|
|
Duration checkDelay = Duration.zero,
|
|
}) async {
|
|
tester.view.physicalSize = const Size(1080, 2400);
|
|
tester.view.devicePixelRatio = 2;
|
|
addTearDown(tester.view.reset);
|
|
final repos = Repositories(live: false);
|
|
final auth = AuthRepository(
|
|
providers: [_Provider()],
|
|
releaseMode: true,
|
|
needsOnboarding: (_) => Future.delayed(checkDelay, () => needs),
|
|
introPending: intro,
|
|
);
|
|
addTearDown(auth.dispose);
|
|
await tester.pumpWidget(FancakeApp(auth: auth, repositories: repos));
|
|
await tester.pumpAndSettle();
|
|
return (auth, repos.onboarding as SampleOnboardingRepository);
|
|
}
|
|
|
|
testWidgets('「로그인」으로 들어온 신규 계정도 약관부터 — 셸로 가지 않는다', (tester) async {
|
|
final (auth, _) = await boot(tester, needs: true);
|
|
await auth.signIn(SocialProvider.google);
|
|
await tester.pumpAndSettle();
|
|
expect(auth.onboarding, OnboardingGate.required);
|
|
expect(find.byType(NavigationBar), findsNothing);
|
|
expect(find.text('개인정보 수집·이용 및 처리방침 동의 (필수)'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('약관 동의가 있는 계정은 바로 셸', (tester) async {
|
|
final (auth, _) = await boot(tester, needs: false);
|
|
await auth.signIn(SocialProvider.google);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(NavigationBar), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('동의한 계정은 셸로 가는 동안 약관 화면이 한 프레임도 그려지지 않는다 — 10/6 콜드 스타트 깜빡임', (
|
|
tester,
|
|
) async {
|
|
// 서버 판정이 몇 프레임 걸리는 실기기처럼 늦춘다.
|
|
final (auth, _) = await boot(
|
|
tester,
|
|
needs: false,
|
|
checkDelay: const Duration(milliseconds: 200),
|
|
);
|
|
unawaited(auth.signIn(SocialProvider.google));
|
|
for (var i = 0; i < 60; i++) {
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
expect(find.byType(OnboardingFlow), findsNothing);
|
|
}
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(NavigationBar), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('약관 → 프로필 → 관심 아티스트 → 행성 가입 → 권한 → 가입 완료 → 셸', (tester) async {
|
|
final (auth, repo) = await boot(tester, needs: true);
|
|
await auth.signIn(SocialProvider.google);
|
|
await tester.pumpAndSettle();
|
|
|
|
// ② 약관 — 필수만 동의(마케팅 미동의).
|
|
for (final label in [
|
|
'서비스 이용약관 (필수)',
|
|
'개인정보 수집·이용 및 처리방침 동의 (필수)',
|
|
'위치기반 서비스 이용약관 (필수)',
|
|
'만 14세 이상입니다. (필수)',
|
|
]) {
|
|
await tester.tap(find.text(label));
|
|
}
|
|
await tester.pump();
|
|
await tester.tap(find.text('완료'));
|
|
await tester.pumpAndSettle();
|
|
expect(repo.agreed, {'service', 'privacy', 'location', 'age'});
|
|
|
|
// ③ 프로필
|
|
await tester.enterText(find.byType(TextField).first, '우주팬');
|
|
await tester.enterText(find.byType(TextField).last, 'ABCDEFGHJK');
|
|
await tester.pump();
|
|
await tester.tap(find.text('다음'));
|
|
await tester.pumpAndSettle();
|
|
expect(repo.profile?.nickname, '우주팬');
|
|
expect(repo.profile?.country, isNotEmpty);
|
|
expect(repo.referral, 'ABCDEFGHJK');
|
|
|
|
// ④ 관심 아티스트 — 최소 1개 전에는 다음이 잠겨 있다.
|
|
final next = find.widgetWithText(FilledButton, '다음');
|
|
expect(tester.widget<FilledButton>(next).onPressed, isNull);
|
|
await tester.tap(find.text('NewJeans'));
|
|
await tester.tap(find.text('BTS'));
|
|
await tester.pump();
|
|
await tester.tap(next);
|
|
await tester.pumpAndSettle();
|
|
|
|
// ⑤ 행성 가입 — 고른 순서(첫 번째가 대표).
|
|
await tester.tap(find.text('선택한 행성 가입하기'));
|
|
await tester.pumpAndSettle();
|
|
expect(repo.joined, ['newjeans', 'bts']);
|
|
|
|
// ⑥ 권한 안내 → 가입 완료(182:29344) → 셸
|
|
expect(find.text('앱 권한 안내'), findsOneWidget);
|
|
await tester.tap(find.text('계속'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('회원가입이 완료되었습니다'), findsOneWidget);
|
|
await tester.tap(find.text('시작'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(NavigationBar), findsOneWidget);
|
|
expect(
|
|
(await SharedPreferences.getInstance()).getBool('coach.pending'),
|
|
isNotNull,
|
|
);
|
|
});
|
|
|
|
testWidgets('프로필 국가 — 사본 국가 리스트 모달(178:23489) 대로 국가명 · 단수 선택 · 「선택」 후 적용', (
|
|
tester,
|
|
) async {
|
|
final (auth, repo) = await boot(tester, needs: true);
|
|
await auth.signIn(SocialProvider.google);
|
|
await tester.pumpAndSettle();
|
|
for (final label in [
|
|
'서비스 이용약관 (필수)',
|
|
'개인정보 수집·이용 및 처리방침 동의 (필수)',
|
|
'위치기반 서비스 이용약관 (필수)',
|
|
'만 14세 이상입니다. (필수)',
|
|
]) {
|
|
await tester.tap(find.text(label));
|
|
}
|
|
await tester.pump();
|
|
await tester.tap(find.text('완료'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(
|
|
find.ancestor(of: find.text('거주 국가/지역'), matching: find.byType(InkWell)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('거주 국가/지역 선택'), findsOneWidget);
|
|
await tester.scrollUntilVisible(
|
|
find.text('대한민국'),
|
|
300,
|
|
scrollable: find.byType(Scrollable).last,
|
|
);
|
|
await tester.tap(find.text('대한민국'));
|
|
await tester.pump();
|
|
await tester.tap(find.widgetWithText(FilledButton, '선택'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.textContaining('대한민국'), findsOneWidget);
|
|
|
|
await tester.enterText(find.byType(TextField).first, '우주팬');
|
|
await tester.pump();
|
|
await tester.tap(find.text('다음'));
|
|
await tester.pumpAndSettle();
|
|
expect(repo.profile?.country, 'KR');
|
|
});
|
|
|
|
testWidgets('프로필 뒤로가기(511:86825) — 약관으로 돌아가 동의가 체크된 채 보이고 다시 기록하지 않는다', (
|
|
tester,
|
|
) async {
|
|
final (auth, repo) = await boot(tester, needs: true);
|
|
await auth.signIn(SocialProvider.google);
|
|
await tester.pumpAndSettle();
|
|
for (final label in [
|
|
'서비스 이용약관 (필수)',
|
|
'개인정보 수집·이용 및 처리방침 동의 (필수)',
|
|
'위치기반 서비스 이용약관 (필수)',
|
|
'만 14세 이상입니다. (필수)',
|
|
]) {
|
|
await tester.tap(find.text(label));
|
|
}
|
|
await tester.pump();
|
|
await tester.tap(find.text('완료'));
|
|
await tester.pumpAndSettle();
|
|
final first = repo.agreed;
|
|
|
|
await tester.tap(find.byType(BackButton));
|
|
await tester.pumpAndSettle();
|
|
// 필수 4항이 이미 체크돼 있어 「완료」가 바로 켜져 있다.
|
|
final done = find.widgetWithText(FilledButton, '완료');
|
|
expect(tester.widget<FilledButton>(done).onPressed, isNotNull);
|
|
await tester.tap(done);
|
|
await tester.pumpAndSettle();
|
|
expect(identical(repo.agreed, first), isTrue);
|
|
expect(find.byType(BackButton), findsOneWidget); // ③ 프로필로 돌아왔다.
|
|
});
|
|
|
|
testWidgets('추천인 코드(C-119) — 귀속 뒤 돌아와도 다시 부르지 않고, 다른 코드는 511:86845 문구로 거절', (
|
|
tester,
|
|
) async {
|
|
final (auth, repo) = await boot(tester, needs: true);
|
|
await auth.signIn(SocialProvider.google);
|
|
await tester.pumpAndSettle();
|
|
for (final label in [
|
|
'서비스 이용약관 (필수)',
|
|
'개인정보 수집·이용 및 처리방침 동의 (필수)',
|
|
'위치기반 서비스 이용약관 (필수)',
|
|
'만 14세 이상입니다. (필수)',
|
|
]) {
|
|
await tester.tap(find.text(label));
|
|
}
|
|
await tester.pump();
|
|
await tester.tap(find.text('완료'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.enterText(find.byType(TextField).first, '우주팬');
|
|
await tester.enterText(find.byType(TextField).last, 'abcdefghjk');
|
|
await tester.pump();
|
|
await tester.tap(find.text('다음'));
|
|
await tester.pumpAndSettle();
|
|
expect(repo.referral, 'ABCDEFGHJK');
|
|
|
|
// ④ → ③ : 귀속한 코드가 채워져 있고 그대로 「다음」이면 통과(두 번째 호출 없음).
|
|
await tester.tap(find.byType(BackButton));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('ABCDEFGHJK'), findsOneWidget);
|
|
// 목 저장소는 저장한 닉네임을 돌려주지 않는다 — 다시 넣는다.
|
|
await tester.enterText(find.byType(TextField).first, '우주팬');
|
|
await tester.pump();
|
|
await tester.tap(find.text('다음'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('NewJeans'), findsOneWidget); // ④ 로 넘어갔다.
|
|
|
|
// 다른 코드로 바꾸면 서버가 1인 1회로 거절 → Figma 오류 문구, 단계는 그대로.
|
|
await tester.tap(find.byType(BackButton));
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(find.byType(TextField).first, '우주팬');
|
|
await tester.enterText(find.byType(TextField).last, 'ZZZZZZZZZZ');
|
|
await tester.pump();
|
|
await tester.tap(find.text('다음'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('추천인 코드를 확인해 주세요.'), findsOneWidget);
|
|
expect(repo.referral, 'ABCDEFGHJK');
|
|
});
|
|
|
|
testWidgets('가입 모드에서 받은 약관은 다시 묻지 않고 기록만 한다', (tester) async {
|
|
final (auth, repo) = await boot(tester, needs: true);
|
|
auth.pendingConsent = {
|
|
'service',
|
|
'privacy',
|
|
'location',
|
|
'age',
|
|
'marketing',
|
|
};
|
|
await auth.signIn(SocialProvider.google, signUp: true);
|
|
await tester.pumpAndSettle();
|
|
expect(repo.agreed, contains('marketing'));
|
|
expect(find.text('거주 국가/지역'), findsOneWidget); // ③ 프로필
|
|
expect(find.text('회원가입'), findsOneWidget); // 511:86825 머리줄 가운데 타이틀
|
|
});
|
|
|
|
test('국가명은 OS 이름 · OS 정렬 순서 — 이름 없는 나라도 코드로 보이지 않는다', () async {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
const channel = MethodChannel('kr.co.fanuzu.app/locale');
|
|
final messenger =
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
|
String? asked;
|
|
messenger.setMockMethodCallHandler(channel, (call) async {
|
|
asked = (call.arguments as Map)['locale'] as String;
|
|
return [
|
|
['GH', '가나'],
|
|
['AD', '안도라'],
|
|
['KR', '대한민국'],
|
|
];
|
|
});
|
|
addTearDown(() {
|
|
messenger.setMockMethodCallHandler(channel, null);
|
|
platformCountryNames = const {};
|
|
});
|
|
|
|
await loadCountryNames(const Locale('ko'));
|
|
expect(asked, 'ko');
|
|
expect(countryName('AD'), '안도라');
|
|
expect(sortedCountries().take(3), ['GH', 'AD', 'KR']);
|
|
expect(sortedCountries().length, isoCountries.length);
|
|
});
|
|
|
|
testWidgets('첫 실행은 소개 3장 — 건너뛰기를 눌러도 로그인 화면', (tester) async {
|
|
final (auth, _) = await boot(tester, needs: false, intro: true);
|
|
expect(find.text('우리 팬덤의 행성을 키워요'), findsOneWidget);
|
|
await tester.tap(find.text('건너뛰기'));
|
|
await tester.pumpAndSettle();
|
|
expect(auth.introPending, isFalse);
|
|
expect(find.byKey(loginLogoKey), findsOneWidget);
|
|
});
|
|
}
|
|
|
|
final class _Provider implements SocialAuthProvider {
|
|
@override
|
|
AuthProviderKind get provider => SocialProvider.google;
|
|
@override
|
|
bool get isLive => true;
|
|
@override
|
|
List<String> get missingConfig => const [];
|
|
@override
|
|
Future<AuthOutcome> signIn() async => const AuthSucceeded(
|
|
AuthAccount(uid: 'onb-user', provider: SocialProvider.google),
|
|
);
|
|
@override
|
|
Future<void> signOut() async {}
|
|
}
|