252 lines
9.9 KiB
Dart
252 lines
9.9 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.textContaining('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('갤럭시 Zap 은 고른 행성이 가입 팬덤이면 그 행성, 아니면 대표 행성으로 간다 (10/3 #10)', () {
|
|
const mine = PlanetNode(id: 'a', name: 'A', rank: 1, fpi: 1, members: 1, online: 1, isMine: true);
|
|
const joined = PlanetNode(id: 'b', name: 'B', rank: 2, fpi: 1, members: 1, online: 1);
|
|
const other = PlanetNode(id: 'c', name: 'C', rank: 3, fpi: 1, members: 1, online: 1);
|
|
const snap = GalaxySnapshot(planets: [mine, joined, other], myPop: 0, joinedIds: {'a', 'b'});
|
|
expect(snap.zapTargetFor(joined)?.id, 'b');
|
|
expect(snap.zapTargetFor(other)?.id, 'a');
|
|
expect(snap.zapTargetFor(null)?.id, 'a');
|
|
expect(snap.rankingView.zapTargetFor(joined)?.id, 'b');
|
|
});
|
|
|
|
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.textContaining('37 POP 이 '), findsOneWidget);
|
|
await tester.pump(const Duration(seconds: 2));
|
|
expect(find.textContaining('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.textContaining('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();
|
|
expect(repository.counts, [1, 1]);
|
|
expect(repository.keys[1], isNot(repository.keys[0]));
|
|
expect(find.textContaining('기여 완료'), findsNothing);
|
|
repository.replies.last.complete(
|
|
const ZapResult(txId: 'confirmed-2', balanceAfter: 12520),
|
|
);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.textContaining('기여 완료'), findsOneWidget);
|
|
expect(repository.replies, hasLength(2));
|
|
});
|
|
|
|
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);
|
|
});
|
|
}
|