GIVE 연타에 행성이 흔들리고 우하단에 [+1] · 10회 이상이면 [+10]과 자전 1.5배, 전송 중 연타는 다음 묶음으로 쌓임

This commit is contained in:
Sn-Kinos
2026-10-22 01:25:27 +09:00
parent 3f59c5070e
commit a2739a37f1
7 changed files with 389 additions and 45 deletions
+9 -1
View File
@@ -200,7 +200,7 @@ void main() {
await tester.pumpAndSettle();
});
testWidgets('전송 중 성공을 보여주지 않고 전송 중 연타로 중복 차감하지 않는다', (tester) async {
testWidgets('전송 중 성공을 보여주지 않고, 전송 중 연타는 앞 묶음 확정 뒤 새 키의 다음 묶음으로 한 번만 보낸다', (tester) async {
final repository = _DelayedGalaxy();
await _show(tester, repository);
await tester.tap(find.byType(ZapButton));
@@ -215,8 +215,16 @@ void main() {
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 {
+185
View File
@@ -0,0 +1,185 @@
import 'dart:async';
import 'package:fancake/core/theme/app_theme.dart';
import 'package:fancake/core/ui/planet_25d.dart';
import 'package:fancake/features/galaxy/galaxy_data.dart';
import 'package:fancake/features/galaxy/widgets/zap_button.dart';
import 'package:fancake/features/galaxy/zap_controller.dart';
import 'package:fancake/features/home/home_data.dart';
import 'package:fancake/features/home/home_screen.dart';
import 'package:fancake/features/planet/planet_data.dart';
import 'package:fancake/features/planet/planet_detail_screen.dart';
import 'package:fancake/features/quest/quest_data.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'eclipse_stubs.dart';
/// 서버 응답을 시험이 직접 풀어 주는 기여 저장소.
class _GatedGalaxy extends SampleGalaxyRepository {
_GatedGalaxy();
final counts = <int>[];
final fandoms = <String>[];
final gates = <Completer<ZapResult>>[];
@override
Future<ZapResult> zap({
required String fandomId,
required int count,
required String clientSeq,
}) {
counts.add(count);
fandoms.add(fandomId);
gates.add(Completer());
return gates.last.future;
}
}
/// 바로 확정하는 기여 저장소.
class _OkGalaxy extends SampleGalaxyRepository {
const _OkGalaxy();
@override
Future<ZapResult> zap({
required String fandomId,
required int count,
required String clientSeq,
}) async => ZapResult(txId: clientSeq, balanceAfter: 100000);
}
/// Figma `2033:15818` 행성 리액션 1 · `2024:20758` 행성 2단계 — GIVE 1회 흔들림 1번 · [+1] 0.5초(연속 가능),
/// 10회 이상 흔들림 x2 · 자전 1.5배 · [+10].
void main() {
group('ZapController 연타', () {
testWidgets('전송 중에 누른 GIVE 도 버리지 않고 다음 묶음으로 보낸다', (tester) async {
final repo = _GatedGalaxy();
final zap = ZapController(repo, onFailure: (_) {});
for (var i = 0; i < 3; i++) {
zap.tap('f1');
}
await tester.pump(const Duration(milliseconds: 450));
expect(repo.counts, [3]);
expect(zap.sending, isTrue);
zap.tap('f1');
zap.tap('f1');
expect(zap.pending, 2);
expect(zap.queued, 5);
await tester.pump(const Duration(milliseconds: 450));
expect(repo.counts, [3], reason: '앞 묶음이 확정되기 전에는 다음 묶음을 보내지 않는다');
repo.gates.first.complete(const ZapResult(txId: 'a', balanceAfter: 10));
await tester.pump();
expect(repo.counts, [3, 2]);
repo.gates.last.complete(const ZapResult(txId: 'b', balanceAfter: 8));
await tester.pump();
expect(zap.queued, 0);
zap.dispose();
});
testWidgets('보내는 중에 다른 행성으로 누른 연타가 앞 행성으로 가지 않는다', (tester) async {
final repo = _GatedGalaxy();
final zap = ZapController(repo, onFailure: (_) {});
zap.tap('a');
await tester.pump(const Duration(milliseconds: 450));
zap.tap('b');
zap.tap('a');
expect(zap.pending, 1, reason: '쌓인 b 묶음에 a 를 섞지 않는다');
await tester.pump(const Duration(milliseconds: 450));
repo.gates.first.complete(const ZapResult(txId: 'a1', balanceAfter: 10));
await tester.pump();
expect(repo.fandoms, ['a', 'b']);
repo.gates.last.complete(const ZapResult(txId: 'b1', balanceAfter: 9));
await tester.pump();
zap.dispose();
});
testWidgets('연속 횟수는 [+1] 이 떠 있는 0.5초 안에 다시 누를 때만 이어진다', (tester) async {
final zap = ZapController(_GatedGalaxy(), onFailure: (_) {});
zap.tap('f1');
await tester.pump(const Duration(milliseconds: 300));
zap.tap('f1');
expect(zap.streak, 2);
await tester.pump(const Duration(milliseconds: 600));
zap.tap('f1');
expect(zap.streak, 1);
zap.dispose();
});
});
group('HOME 행성 GIVE 반응', () {
Future<void> pumpHome(WidgetTester tester) async {
tester.view.physicalSize = const Size(1125, 2436);
tester.view.devicePixelRatio = 3;
addTearDown(tester.view.reset);
await tester.pumpWidget(
MaterialApp(
theme: buildAppTheme(),
home: HomeScreen(
repository: const SampleHomeRepository(),
questRepository: const SampleQuestRepository(),
eclipse: const NoEclipseRepository(),
galaxyRepository: const _OkGalaxy(),
planetRepository: samplePlanetRepository,
),
),
);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
}
double shake(WidgetTester tester) => tester
.widget<Transform>(find.byKey(const ValueKey('planet-give-shake')))
.transform
.getTranslation()
.x;
testWidgets('GIVE 1회: 행성이 흔들리고 행성 우하단에 [+1] 이 0.5초 뜬다', (tester) async {
await pumpHome(tester);
await tester.tap(find.byType(ZapButton));
await tester.pump();
await tester.pump(const Duration(milliseconds: 60));
expect(shake(tester), isNot(0));
final label = find.byKey(const ValueKey('planet-give-count'));
expect(tester.widget<Text>(label).data, '+1');
final planet = tester.getRect(find.byType(Planet25D).first);
final at = tester.getCenter(label);
expect(at.dx, greaterThan(planet.center.dx));
expect(at.dy, greaterThan(planet.center.dy));
await tester.pump(const Duration(milliseconds: 450));
expect(label, findsNothing);
await tester.pump(const Duration(seconds: 3));
});
testWidgets('행성 터치 직후 GIVE 가 흔들림을 다시 시작해도 커뮤니티 이동은 간다', (tester) async {
await pumpHome(tester);
await tester.tap(find.byType(Planet25D).first, warnIfMissed: false);
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 400));
await tester.pump(const Duration(seconds: 1));
expect(find.byType(PlanetDetailScreen), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
});
testWidgets('GIVE 10회 이상: [+10] · 자전 1.5배(Planet25D.spinBoost)', (tester) async {
await pumpHome(tester);
for (var i = 0; i < 10; i++) {
await tester.tap(find.byType(ZapButton));
await tester.pump(const Duration(milliseconds: 120));
}
expect(find.text('+10'), findsOneWidget);
expect(
tester.widget<Planet25D>(find.byType(Planet25D).first).spinBoost,
isTrue,
);
await tester.pump(const Duration(milliseconds: 600));
expect(
tester.widget<Planet25D>(find.byType(Planet25D).first).spinBoost,
isFalse,
);
await tester.pump(const Duration(seconds: 3));
});
});
}