Music Shake 첫 박 시각 · 카운트다운 · 옛 일시 정지 판 정리
This commit is contained in:
@@ -263,10 +263,10 @@ void main() {
|
||||
test('일시 정지 세션은 HOME 줄에도 「분류 · 일시 정지 · n/m」로 보인다 — 10/6 A-1', () async {
|
||||
final db = await _seed();
|
||||
await db.doc('quests/shake').set({
|
||||
'active': true, 'title': '박자 맞춰 흔들기', 'type': 'musicShake',
|
||||
'active': true, 'title': '박자 맞춰 흔들기', 'type': 'musicShake', 'totalSegments': 32,
|
||||
});
|
||||
await db.doc('users/$_uid/questProgress/shake').set({
|
||||
'session': {'state': 'paused', 'progress': 14, 'total': 32},
|
||||
'session': {'state': 'paused', 'progress': 14, 'total': 32, 'firstBeatMs': 0},
|
||||
});
|
||||
final quest = (await FirestoreHomeRepository(_backend(db)).load()).quests.single;
|
||||
expect(
|
||||
|
||||
@@ -19,6 +19,36 @@ void main() {
|
||||
segments: 3,
|
||||
);
|
||||
|
||||
test('첫 박 · 판 길이가 바뀐 뒤의 옛 일시 정지는 이어하지 않는다 (10/6 대표 지적)', () {
|
||||
const fresh = MusicShakeSetup(
|
||||
videoId: 'v', title: 't', bpm: 114, segments: 368,
|
||||
firstBeat: Duration(milliseconds: 24276),
|
||||
);
|
||||
// 첫 박 도입 전(32박, 첫 박 0)에 저장된 판.
|
||||
const old = QuestSession(state: QuestStage.paused, progress: 19, total: 32);
|
||||
expect(old.fitsShake(fresh), isFalse);
|
||||
const same = QuestSession(
|
||||
state: QuestStage.paused, progress: 19, total: 368,
|
||||
firstBeat: Duration(milliseconds: 24276),
|
||||
);
|
||||
expect(same.fitsShake(fresh), isTrue);
|
||||
});
|
||||
|
||||
test('새 판은 첫 박 leadIn 전부터 틀고, 첫 박이 더 이르면 0 이다 (10/6 B안)', () {
|
||||
const s = MusicShakeSetup(
|
||||
videoId: 'v', title: 't', bpm: 114, segments: 368,
|
||||
firstBeat: Duration(milliseconds: 24276),
|
||||
);
|
||||
expect(s.startAt, const Duration(milliseconds: 21276));
|
||||
expect(
|
||||
const MusicShakeSetup(
|
||||
videoId: 'v', title: 't', bpm: 60, segments: 3,
|
||||
firstBeat: Duration(seconds: 2),
|
||||
).startAt,
|
||||
Duration.zero,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('재생 중 흔들기만 영상 시각으로 판정 · 끝나면 판정표를 돌려준다', (tester) async {
|
||||
final shakes = StreamController<DateTime>();
|
||||
addTearDown(shakes.close);
|
||||
@@ -35,6 +65,7 @@ void main() {
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MusicShakePlayScreen(
|
||||
setup: setup,
|
||||
countdown: Duration.zero,
|
||||
shakes: shakes.stream,
|
||||
player: (onEvent) {
|
||||
player = onEvent;
|
||||
@@ -141,6 +172,7 @@ void main() {
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MusicShakePlayScreen(
|
||||
setup: setup,
|
||||
countdown: Duration.zero,
|
||||
shakes: shakes.stream,
|
||||
judge: judge,
|
||||
player: (onEvent) {
|
||||
@@ -181,4 +213,48 @@ void main() {
|
||||
expect(resume, contains('player.seekTo(9.91,true);player.playVideo();'));
|
||||
expect(YoutubeEmbed.html('vid'), isNot(contains('playVideo')));
|
||||
});
|
||||
|
||||
testWidgets('재생을 시작하면 3·2·1 을 세는 동안 판정하지 않고, 0 이 되면 판정이 시작된다', (tester) async {
|
||||
final shakes = StreamController<DateTime>();
|
||||
addTearDown(shakes.close);
|
||||
late ValueChanged<ShakePlayerEvent> player;
|
||||
final judge = ShakeJudge(bpm: 60, segments: 3);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: buildAppTheme(),
|
||||
home: MusicShakePlayScreen(
|
||||
setup: setup,
|
||||
countdown: const Duration(milliseconds: 500), // 화면은 단말 시계로 센다
|
||||
shakes: shakes.stream,
|
||||
judge: judge,
|
||||
player: (onEvent) {
|
||||
player = onEvent;
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
player((playing: true, position: Duration.zero, ended: false));
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
shakes.add(DateTime.now()); // 세는 동안의 흔들기는 세지 않는다.
|
||||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 700)));
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(find.text('1'), findsNothing);
|
||||
player((playing: true, position: Duration.zero, ended: false));
|
||||
shakes.add(DateTime.now());
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(judge.hits[0], inInclusiveRange(0, 0.01));
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
});
|
||||
|
||||
test('박은 firstBeat 부터 — 인트로의 흔들기는 박이 아니다', () {
|
||||
final j = ShakeJudge(bpm: 60, segments: 2, firstBeat: const Duration(seconds: 17))
|
||||
..add(const Duration(seconds: 1))
|
||||
..add(const Duration(seconds: 17))
|
||||
..add(const Duration(milliseconds: 18300));
|
||||
expect(j.end, const Duration(milliseconds: 18500));
|
||||
expect(j.judgement(), {'perfect': 1, 'good': 1, 'miss': 0, 'maxCombo': 2});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ service cloud.firestore {
|
||||
}, SetOptions(merge: true));
|
||||
await db.doc('quests/shake').set({
|
||||
'type': 'musicshake', 'title': '응원 흔들기', 'dailyLimit': 1,
|
||||
'totalSegments': 3, 'bpm': 120,
|
||||
'totalSegments': 3, 'bpm': 120, 'firstBeatMs': 17000,
|
||||
'link': {'url': 'https://youtu.be/WMweEpGlu_U', 'title': 'BTS - Butter'},
|
||||
});
|
||||
final backend = _RecordingBackend(db);
|
||||
@@ -423,20 +423,22 @@ service cloud.firestore {
|
||||
expect(setup.videoId, 'WMweEpGlu_U');
|
||||
expect(setup.title, 'BTS - Butter');
|
||||
expect((setup.bpm, setup.segments), (120.0, 3));
|
||||
expect(setup.firstBeat, const Duration(seconds: 17));
|
||||
expect((setup.windows.perfect, setup.windows.good), (0.1, 0.4));
|
||||
|
||||
// 판을 끝내기 전에는 서버를 부르지 않는다.
|
||||
await expectLater(repo.verify(card), throwsA(isA<BackendException>()));
|
||||
expect(backend.calls, isEmpty);
|
||||
|
||||
repo.recordShake(ShakeJudge(bpm: 120, segments: 3, windows: setup.windows)
|
||||
..add(Duration.zero));
|
||||
repo.recordShake(ShakeJudge(bpm: 120, segments: 3, firstBeat: setup.firstBeat, windows: setup.windows)
|
||||
..add(setup.firstBeat));
|
||||
await repo.verify(card);
|
||||
final call = backend.calls.single;
|
||||
expect(call.name, 'fnQuestVerifyMusicShake');
|
||||
expect(call.payload['questId'], 'shake');
|
||||
expect(call.payload['trackId'], 'WMweEpGlu_U');
|
||||
expect(call.payload['totalSegments'], 3);
|
||||
expect(call.payload['firstBeatMs'], 17000);
|
||||
expect(call.payload['judgement'], {'perfect': 1, 'good': 0, 'miss': 2, 'maxCombo': 1});
|
||||
});
|
||||
|
||||
@@ -661,7 +663,7 @@ service cloud.firestore {
|
||||
final ref = db.doc('users/$_uid/questProgress/shake');
|
||||
final repo = FirestoreQuestRepository(_RecordingBackend(db));
|
||||
|
||||
await ref.set({'session': {'state': 'inProgress', 'progress': 43, 'total': 60}});
|
||||
await ref.set({'session': {'state': 'inProgress', 'progress': 43, 'total': 60, 'firstBeatMs': 0}});
|
||||
var card = _card(await repo.load(), 'shake');
|
||||
// 앱이 꺼진 진행중도 진행 화면 밖이므로 일시 정지다.
|
||||
expect(card.stage, QuestStage.paused);
|
||||
@@ -669,7 +671,7 @@ service cloud.firestore {
|
||||
expect(QuestTab.inProgress.accepts(card), isTrue);
|
||||
|
||||
await ref.set({'session': {
|
||||
'state': 'completed', 'progress': 60, 'total': 60,
|
||||
'state': 'completed', 'progress': 60, 'total': 60, 'firstBeatMs': 0,
|
||||
'judgement': {'perfect': 50, 'good': 5, 'miss': 5, 'maxCombo': 30},
|
||||
}});
|
||||
card = _card(await repo.load(), 'shake');
|
||||
@@ -680,7 +682,7 @@ service cloud.firestore {
|
||||
test('목록에서 바로 받는 Music Shake 는 서버 세션의 판정표를 보낸다', () async {
|
||||
final db = await shakeSeed();
|
||||
await db.doc('users/$_uid/questProgress/shake').set({'session': {
|
||||
'state': 'completed', 'progress': 60, 'total': 60,
|
||||
'state': 'completed', 'progress': 60, 'total': 60, 'firstBeatMs': 0,
|
||||
'judgement': {'perfect': 50, 'good': 5, 'miss': 5, 'maxCombo': 30},
|
||||
}});
|
||||
final backend = _RecordingBackend(db);
|
||||
@@ -733,11 +735,11 @@ service cloud.firestore {
|
||||
'type': 'musicshake', 'title': '두 번째', 'totalSegments': 30, 'bpm': 100,
|
||||
});
|
||||
await db.doc('users/$_uid/questProgress/shake').set({'session': {
|
||||
'state': 'paused', 'progress': 43, 'total': 60,
|
||||
'state': 'paused', 'progress': 43, 'total': 60, 'firstBeatMs': 0,
|
||||
'updatedAt': Timestamp.fromMillisecondsSinceEpoch(2000),
|
||||
}});
|
||||
await db.doc('users/$_uid/questProgress/shake2').set({'session': {
|
||||
'state': 'paused', 'progress': 3, 'total': 30,
|
||||
'state': 'paused', 'progress': 3, 'total': 30, 'firstBeatMs': 0,
|
||||
'updatedAt': Timestamp.fromMillisecondsSinceEpoch(1000),
|
||||
}});
|
||||
final repo = FirestoreQuestRepository(_RecordingBackend(db));
|
||||
@@ -745,5 +747,23 @@ service cloud.firestore {
|
||||
expect(card?.id, 'shake');
|
||||
expect((card?.session?.progress, card?.session?.total), (43, 60));
|
||||
});
|
||||
|
||||
test('운영값이 바뀐 옛 Music Shake 판은 일시 정지로 보이지 않고 시작하기다 (10/6)', () async {
|
||||
final db = await shakeSeed();
|
||||
await db.doc('quests/shake').update({'firstBeatMs': 24276});
|
||||
await db.doc('users/$_uid/questProgress/shake').set({'session': {
|
||||
'state': 'paused', 'progress': 43, 'total': 60, 'firstBeatMs': 0,
|
||||
}});
|
||||
final repo = FirestoreQuestRepository(_RecordingBackend(db));
|
||||
final card = _card(await repo.load(), 'shake');
|
||||
expect((card.stage, card.session, card.staleShake), (QuestStage.notStarted, null, true));
|
||||
expect(await repo.pausedQuest(), isNull);
|
||||
// 맞는 판은 그대로 일시 정지다.
|
||||
await db.doc('users/$_uid/questProgress/shake').set({'session': {
|
||||
'state': 'paused', 'progress': 43, 'total': 60, 'firstBeatMs': 24276,
|
||||
}});
|
||||
final ok = _card(await repo.load(), 'shake');
|
||||
expect((ok.stage, ok.staleShake), (QuestStage.paused, false));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user