Zap·후원 FPI 귀속 · 이클립스 방어 Shake 전용 · 가입 팬덤만 방어

This commit is contained in:
Sn-Kinos
2026-10-05 21:53:20 +09:00
parent 17c658b272
commit 71ec17dc3c
21 changed files with 196 additions and 134 deletions
+14 -9
View File
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sensors_plus/sensors_plus.dart' show SensorInterval;
@@ -24,6 +26,9 @@ Future<void> _pump(WidgetTester tester, Widget home) async {
}
}
/// 가짜 가속도 센서 — 흔들기 한 번마다 이벤트 하나.
final _shakes = StreamController<DateTime>.broadcast();
void main() {
group('흔들기 판정 (iOS 16 · Galaxy 63 실기기 편차)', () {
test('표본 주기를 명시한다 — 기본 200ms(iOS 5Hz)를 쓰지 않는다', () {
@@ -147,10 +152,10 @@ void main() {
),
);
addTearDown(repo.dispose);
await _pump(tester, EclipseDefendScreen(repository: repo, fandomId: 'bts'));
expect(find.text('휴대폰을 흔들어\n행성 운석 충돌을 막으세요!'), findsOneWidget);
await _pump(tester, EclipseDefendScreen(repository: repo, fandomId: 'bts', shakes: () => _shakes.stream));
expect(find.text('휴대폰을 흔들어\n이클립스를 막으세요!'), findsOneWidget);
expect(find.textContaining('이클립스 발생 남은 시간'), findsOneWidget);
expect(find.text('흔들기 어려우면 눌러서 참여'), findsNothing);
expect(find.text('이 기기는 흔들기 방어를 지원하지 않아요'), findsNothing);
});
testWidgets('발생 3초 전부터 오버레이 카운트다운', (tester) async {
@@ -162,8 +167,8 @@ void main() {
),
);
addTearDown(repo.dispose);
await _pump(tester, EclipseDefendScreen(repository: repo, fandomId: 'bts'));
expect(find.text('행성 운석 충돌이\n시작됩니다!'), findsOneWidget);
await _pump(tester, EclipseDefendScreen(repository: repo, fandomId: 'bts', shakes: () => _shakes.stream));
expect(find.text('행성에 이클립스가\n시작됩니다!'), findsOneWidget);
expect(find.text('3'), findsOneWidget);
});
@@ -177,12 +182,12 @@ void main() {
),
);
addTearDown(repo.dispose);
await _pump(tester, EclipseDefendScreen(repository: repo, fandomId: 'bts'));
await _pump(tester, EclipseDefendScreen(repository: repo, fandomId: 'bts', shakes: () => _shakes.stream));
expect(find.textContaining('초간 휴대폰을 흔들어'), findsOneWidget);
expect(find.text('내 유효 Shake 0 / 60회'), findsOneWidget);
expect(find.text('유효 참여 3 / 10명'), findsOneWidget);
await tester.tap(find.text('흔들기 어려우면 눌러서 참여'));
_shakes.add(DateTime.now());
for (var i = 0; i < 8; i++) {
await tester.pump(const Duration(milliseconds: 120));
}
@@ -196,8 +201,8 @@ void main() {
state: eclipseFixture(scheduled: true, startAt: startAt, endsAt: endsAt),
);
addTearDown(repo.dispose);
await _pump(tester, EclipseDefendScreen(repository: repo, fandomId: 'bts'));
await tester.tap(find.text('흔들기 어려우면 눌러서 참여'));
await _pump(tester, EclipseDefendScreen(repository: repo, fandomId: 'bts', shakes: () => _shakes.stream));
_shakes.add(DateTime.now());
for (var i = 0; i < 8; i++) {
await tester.pump(const Duration(milliseconds: 120));
}
+28 -12
View File
@@ -56,7 +56,7 @@ Future<void> _pumpGalaxy(
}
}
/// 흔들어 방어 화면. 센서가 없는 테스트 환경이라 눌러서 참여하는 경로로 본다.
/// 흔들어 방어 화면. 센서 대신 가짜 흔들기 스트림을 넣는다 — 방어는 Shake only(FX §5).
Future<void> _pumpDefend(WidgetTester tester, EclipseRepository repo) async {
tester.view.physicalSize = const Size(1080, 2400);
tester.view.devicePixelRatio = 2;
@@ -64,7 +64,7 @@ Future<void> _pumpDefend(WidgetTester tester, EclipseRepository repo) async {
await tester.pumpWidget(
MaterialApp(
theme: buildAppTheme(),
home: EclipseDefendScreen(repository: repo, fandomId: 'bts'),
home: EclipseDefendScreen(repository: repo, fandomId: 'bts', shakes: () => _shakes.stream),
),
);
await tester.pump();
@@ -77,6 +77,9 @@ Future<void> _settle(WidgetTester tester) async {
}
}
/// 가짜 가속도 센서 — 흔들기 한 번마다 이벤트 하나.
final _shakes = StreamController<DateTime>.broadcast();
void main() {
group('이클립스 경보 배너 (별첨 1 ① 「인앱 워닝 배너 병행」)', () {
testWidgets('활성 이클립스가 없으면 배너가 뜨지 않는다', (tester) async {
@@ -367,14 +370,13 @@ void main() {
expect(find.textContaining('흔들어 방어'), findsWidgets);
});
testWidgets('누르면 흔들기가 묶여 서버로 나간다', (tester) async {
testWidgets('흔들기가 묶여 서버로 나간다', (tester) async {
final repo = FakeEclipseRepository(state: eclipseFixture());
addTearDown(repo.dispose);
await _pumpDefend(tester, repo);
// 센서가 없는 단말용 버튼. 판정은 서버가 하므로 흔들기와 같은 경로다.
for (var i = 0; i < 4; i++) {
await tester.tap(find.text('흔들기 어려우면 눌러서 참여'));
for (var i = 0; i < 4; i++) {
_shakes.add(DateTime.now());
await tester.pump(const Duration(milliseconds: 80));
}
await _settle(tester);
@@ -392,7 +394,7 @@ void main() {
// 다섯 번 참여했는데 서버가 둘만 받아들인 상황.
for (var i = 0; i < 5; i++) {
await tester.tap(find.text('흔들기 어려우면 눌러서 참여'));
_shakes.add(DateTime.now());
await tester.pump(const Duration(milliseconds: 80));
}
await _settle(tester);
@@ -406,15 +408,15 @@ void main() {
addTearDown(repo.dispose);
await _pumpDefend(tester, repo);
expect(find.text('흔들기 어려우면 눌러서 참여'), findsNothing);
expect(find.text('운석 충돌을\n성공적으로 막았어요!'), findsOneWidget);
expect(find.text('이 기기는 흔들기 방어를 지원하지 않아요'), findsNothing);
expect(find.text('이클립스를\n성공적으로 막았어요!'), findsOneWidget);
});
testWidgets('마감은 판정 대기이며 서버 실패·취소만 해당 결과로 표시한다', (tester) async {
final repo = FakeEclipseRepository(state: eclipseFixture());
addTearDown(repo.dispose);
await _pumpDefend(tester, repo);
await tester.tap(find.text('흔들기 어려우면 눌러서 참여'));
_shakes.add(DateTime.now());
repo.push(eclipseFixture(endsAt: DateTime.now().subtract(const Duration(seconds: 1))));
await tester.pump();
await _settle(tester);
@@ -437,7 +439,7 @@ void main() {
addTearDown(repo.dispose);
await _pumpDefend(tester, repo);
for (var i = 0; i < 12; i++) {
await tester.tap(find.text('흔들기 어려우면 눌러서 참여'));
_shakes.add(DateTime.now());
await tester.pump(const Duration(milliseconds: 100));
}
expect(repo.calls, isNotEmpty);
@@ -451,7 +453,7 @@ void main() {
..failWith = const BackendException('unavailable', 'private backend error');
addTearDown(repo.dispose);
await _pumpDefend(tester, repo);
await tester.tap(find.text('흔들기 어려우면 눌러서 참여'));
_shakes.add(DateTime.now());
await _settle(tester);
expect(repo.calls, hasLength(1));
final original = repo.calls.single;
@@ -465,6 +467,20 @@ void main() {
expect(find.text('같은 참여 다시 확인'), findsNothing);
});
testWidgets('센서가 없는 단말은 탭 대체 없이 미지원 안내만 (FX §5 Shake only)', (tester) async {
final repo = FakeEclipseRepository(state: eclipseFixture());
addTearDown(repo.dispose);
await tester.pumpWidget(MaterialApp(
theme: buildAppTheme(),
home: EclipseDefendScreen(repository: repo, fandomId: 'bts',
shakes: () => Stream<DateTime>.error(StateError('no sensor'))),
));
await _settle(tester);
expect(find.text('이 기기는 흔들기 방어를 지원하지 않아요'), findsOneWidget);
expect(find.byType(FilledButton), findsNothing);
expect(repo.calls, isEmpty);
});
testWidgets('진행 중인 이클립스가 없으면 그 사실을 알린다', (tester) async {
final repo = FakeEclipseRepository();
addTearDown(repo.dispose);