Files
BlockSeasons/test/state/season_flow_test.dart
T
airkjw bf7720ebd3 fix: clear season flow when starting Classic so tutorial/theme can't leak
- Add SeasonFlowNotifier.clear() to null out the flow state
- Call clear() in HomeScreen's Classic button before startStage()
- Broaden tutorial-end guard to next.phase != GamePhase.playing (covers stuck)
- Add regression test: clear() resets flow to null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:33:41 +09:00

97 lines
3.4 KiB
Dart

import 'package:block_seasons/data/save_repository.dart';
import 'package:block_seasons/game/engine/game_engine.dart';
import 'package:block_seasons/game/models/season.dart';
import 'package:block_seasons/state/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
SeasonPack _pack() => SeasonPack.fromJson({
'schemaVersion': 1,
'seasonId': 'season_test',
'version': 1,
'title': {'en': 'T', 'ko': 'T'},
'theme': {'tileSet': 'spring', 'background': 'bg.webp'},
'stages': [
for (var i = 0; i < 3; i++)
{
'id': 'st_$i',
'seed': 100 + i,
'moveLimit': 20,
'preset': const <Map<String, dynamic>>[],
'objectives': [
{'type': 'reachScore', 'target': 999999},
],
'stars': {
'two': {'movesLeft': 5},
'three': {'movesLeft': 10},
},
'generatorProfile': 'mid',
},
],
});
Future<ProviderContainer> _container() async {
SharedPreferences.setMockInitialValues({});
final repo = SaveRepository(await SharedPreferences.getInstance());
final container = ProviderContainer(
overrides: [saveRepositoryProvider.overrideWithValue(repo)],
);
addTearDown(container.dispose);
return container;
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('starting a season stage spins up the game session', () async {
final container = await _container();
final flow = container.read(seasonFlowProvider.notifier);
flow.startSeasonStage(_pack(), 0);
expect(container.read(seasonFlowProvider)!.index, 0);
expect(container.read(seasonFlowProvider)!.hasNext, isTrue);
final session = container.read(gameSessionProvider)!;
expect(session.phase, GamePhase.playing);
expect(session.movesLeft, 20);
});
test('recordWin persists stars through the progress notifier', () async {
final container = await _container();
final flow = container.read(seasonFlowProvider.notifier);
flow.startSeasonStage(_pack(), 1);
await flow.recordWin(stars: 3, score: 1234);
final repo = container.read(saveRepositoryProvider);
expect(repo.progressFor('season_test', 'st_1')!.stars, 3);
expect(container.read(progressProvider)['season_test/st_1']!.stars, 3);
});
test('nextStage advances and starts the following stage', () async {
final container = await _container();
final flow = container.read(seasonFlowProvider.notifier);
flow.startSeasonStage(_pack(), 1);
flow.nextStage();
expect(container.read(seasonFlowProvider)!.index, 2);
expect(container.read(seasonFlowProvider)!.hasNext, isFalse);
expect(container.read(gameSessionProvider)!.phase, GamePhase.playing);
});
test('clear() resets flow to null so Classic mode has no stale season context',
() async {
final container = await _container();
final flow = container.read(seasonFlowProvider.notifier);
flow.startSeasonStage(_pack(), 0);
// State is non-null after starting a season stage.
expect(container.read(seasonFlowProvider), isNotNull);
flow.clear();
// After clear(), the flow must be null so GameScreen's tutorial/theme
// checks can't fire for a Classic (endless) session.
expect(container.read(seasonFlowProvider), isNull);
});
}