Files
fanuzu/app/test/galaxy_zap_fx_test.dart
T

233 lines
8.8 KiB
Dart

import 'package:fancake/features/home/home_screen.dart';
import 'package:fancake/features/home/home_data.dart';
import 'package:fancake/features/quest/quest_data.dart';
import 'package:fancake/features/galaxy/widgets/zap_feedback.dart';
import 'dart:async';
import 'package:fancake/core/data/backend.dart';
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
import 'package:firebase_auth_mocks/firebase_auth_mocks.dart';
import 'package:fancake/features/galaxy/galaxy_repository_firestore.dart';
import 'package:fancake/core/theme/app_theme.dart';
import 'package:fancake/features/galaxy/galaxy_data.dart';
import 'package:fancake/features/galaxy/galaxy_screen.dart';
import 'package:fancake/features/galaxy/widgets/zap_button.dart';
import 'package:fancake/features/planet/planet_data.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'eclipse_stubs.dart';
class _DelayedGalaxy extends SampleGalaxyRepository {
final replies = <Completer<ZapResult>>[];
final keys = <String>[];
final counts = <int>[];
final fandoms = <String>[];
@override
Future<ZapResult> zap({
required String fandomId,
required int count,
required String clientSeq,
}) {
keys.add(clientSeq);
counts.add(count);
fandoms.add(fandomId);
final reply = Completer<ZapResult>();
replies.add(reply);
return reply.future;
}
}
Future<void> _show(WidgetTester tester, GalaxyRepository repository) async {
await tester.pumpWidget(
MaterialApp(
theme: buildAppTheme(),
home: GalaxyScreen(
repository: repository,
planetRepository: samplePlanetRepository,
eclipse: const NoEclipseRepository(),
),
),
);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
}
class _LedgerReply extends Backend {
_LedgerReply(FakeFirebaseFirestore db)
: super(firestore: db, auth: MockFirebaseAuth());
@override
Future<Map<String, dynamic>> call(
String name, [
Map<String, dynamic> payload = const {},
]) async => {
'txId': 'receipt',
'balanceAfter': 963,
'lotRefs': [
{'lotId': 'a', 'delta': -12},
{'lotId': 'b', 'delta': -25},
],
};
}
void main() {
testWidgets('HOME ZAP 연타·응답유실·확정 결과가 실제 행성에 연결된다', (tester) async {
tester.view.physicalSize = const Size(1080, 2400);
tester.view.devicePixelRatio = 3;
addTearDown(tester.view.reset);
final repository = _DelayedGalaxy();
final home = await const SampleHomeRepository().load();
await tester.pumpWidget(MaterialApp(theme: buildAppTheme(), home: HomeScreen(
repository: const SampleHomeRepository(),
questRepository: const SampleQuestRepository(),
eclipse: const NoEclipseRepository(), galaxyRepository: repository,
)));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.byType(ZapButton), findsOneWidget);
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 350));
expect(repository.counts, [2]);
expect(repository.fandoms, [home.fandomId]);
expect(find.textContaining('기여 완료'), findsNothing);
final fx = tester.widget<ZapFeedback>(find.byType(ZapFeedback));
expect(fx.source.dy, greaterThan(fx.target.dy));
expect(fx.target.dx, greaterThan(0));
repository.replies.single.completeError(const BackendException('unavailable', 'private error'));
await tester.pump();
await tester.tap(find.text('같은 기여 다시 확인'));
await tester.pump();
expect(repository.keys.last, repository.keys.first);
expect(repository.counts, [2, 2]);
repository.replies.last.complete(const ZapResult(txId: 'home-confirmed', balanceAfter: 900, contributedPop: 37));
await tester.pump();
expect(find.text('37 POP 기여 완료'), findsOneWidget);
expect(tester.widget<ZapButton>(find.byType(ZapButton)).myPop, 900);
expect(tester.takeException(), isNull);
await tester.pumpWidget(const SizedBox());
});
testWidgets('Reduce Motion은 갤럭시 자전도 정지하고 해제 시 복구한다', (tester) async {
Future<void> show(bool reduced) => tester.pumpWidget(
MaterialApp(
home: MediaQuery(
data: MediaQueryData(disableAnimations: reduced),
child: GalaxyScreen(
repository: const SampleGalaxyRepository(),
planetRepository: samplePlanetRepository,
eclipse: const NoEclipseRepository(),
),
),
),
);
await show(true);
await tester.pump();
await tester.pump(const Duration(seconds: 3));
await tester.pump(const Duration(seconds: 3));
expect(tester.binding.transientCallbackCount, 0);
await show(false);
await tester.pump(const Duration(milliseconds: 50));
expect(tester.binding.transientCallbackCount, greaterThan(0));
await show(true);
await tester.pump(const Duration(seconds: 1));
expect(tester.binding.transientCallbackCount, 0);
});
test('기여 결과는 현재 단가가 아니라 서버가 확정한 차감 lot 합계다', () async {
final db = FakeFirebaseFirestore();
await db.doc('config/policy').set({'zapPop': 10});
final result = await FirestoreGalaxyRepository(
_LedgerReply(db),
).zap(fandomId: 'bts', count: 1, clientSeq: 'retry');
expect(result.contributedPop, 37);
});
testWidgets('실제 기여량을 표시하고 같은 거래 응답의 성공 연출은 반복하지 않는다', (tester) async {
final repository = _DelayedGalaxy();
await _show(tester, repository);
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 500));
const receipt = ZapResult(
txId: 'receipt',
balanceAfter: 963,
contributedPop: 37,
);
repository.replies.last.complete(receipt);
await tester.pump();
expect(find.text('37 POP 기여 완료'), findsOneWidget);
await tester.pump(const Duration(seconds: 2));
expect(find.text('37 POP 기여 완료'), findsNothing);
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 500));
repository.replies.last.complete(receipt);
await tester.pump();
expect(find.text('37 POP 기여 완료'), findsNothing);
});
testWidgets('ZAP 옵션은 2초 전에 열리지 않고 길게 누르면 차감하지 않는다', (tester) async {
var taps = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: ZapButton(zapPop: 10, myPop: 100, onZap: () => taps++),
),
),
),
);
final hold = await tester.startGesture(
tester.getCenter(find.byType(ZapButton)),
);
await tester.pump(const Duration(milliseconds: 1999));
expect(find.text('아이템'), findsNothing);
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('아이템'), findsOneWidget);
await hold.up();
expect(taps, 0);
await tester.pumpAndSettle();
});
testWidgets('전송 중 성공을 보여주지 않고 전송 중 연타로 중복 차감하지 않는다', (tester) async {
final repository = _DelayedGalaxy();
await _show(tester, repository);
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 500));
expect(repository.replies, hasLength(1));
expect(find.textContaining('기여 반영 중'), findsWidgets);
expect(find.textContaining('기여 완료'), findsNothing);
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 500));
expect(repository.replies, hasLength(1));
repository.replies.single.complete(
const ZapResult(txId: 'confirmed', balanceAfter: 12530),
);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.textContaining('기여 완료'), findsOneWidget);
});
testWidgets('응답 유실 재확인은 같은 멱등키를 쓰고 실패에는 성공 FX가 없다', (tester) async {
final repository = _DelayedGalaxy();
await _show(tester, repository);
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 500));
repository.replies.single.completeError(
const BackendException('unavailable', '연결 실패'),
);
await tester.pump();
expect(find.textContaining('기여 완료'), findsNothing);
await tester.tap(find.text('다시 확인'));
await tester.pump();
expect(repository.keys, hasLength(2));
expect(repository.keys[1], repository.keys[0]);
repository.replies.last.complete(
const ZapResult(txId: 'confirmed', balanceAfter: 12530),
);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.textContaining('기여 완료'), findsOneWidget);
});
}