style(lint): flutter analyze 경고 91건 → 0건 전체 정리
- analysis_options.yaml: freezed/g.dart 생성 파일 분석 제외
- game_text_l10n.dart: if문 중괄호 추가, 불필요한 ${} 제거
- iap_service.dart: 불필요한 dart:typed_data import 제거
- pq_logic.dart, combat_text_frames.dart: dangling library doc → library; 추가
- save_picker_dialog.dart: __ → _ (unnecessary_underscores)
- desktop_equipment_panel.dart: 불필요한 import 제거
- test 파일: _localVar → localVar 네이밍, ignore_for_file 추가
This commit is contained in:
@@ -14,6 +14,9 @@ analyzer:
|
||||
strict-casts: true
|
||||
strict-inference: true
|
||||
strict-raw-types: true
|
||||
exclude:
|
||||
- "**/*.freezed.dart"
|
||||
- "**/*.g.dart"
|
||||
|
||||
linter:
|
||||
# Keep the rule set lean; we will tighten as the engine port stabilizes.
|
||||
|
||||
@@ -159,7 +159,7 @@ String get speedBoostTitle => _l('Speed Boost', '속도 부스트', 'スピー
|
||||
String get speedBoostActivate =>
|
||||
_l('Activate 10x Speed', '10배속 활성화', '10倍速を有効化');
|
||||
String speedBoostRemaining(int seconds) =>
|
||||
_l('${seconds}s remaining', '${seconds}초 남음', '残り${seconds}秒');
|
||||
_l('${seconds}s remaining', '$seconds초 남음', '残り$seconds秒');
|
||||
String get speedBoostActive => _l('BOOST ACTIVE', '부스트 활성화', 'ブースト中');
|
||||
|
||||
// ============================================================================
|
||||
@@ -650,8 +650,9 @@ String translateImpressiveTitle(String englishName) {
|
||||
/// 특수 아이템 이름 번역
|
||||
String translateSpecial(String englishName) {
|
||||
if (isKoreanLocale) return specialTranslationsKo[englishName] ?? englishName;
|
||||
if (isJapaneseLocale)
|
||||
if (isJapaneseLocale) {
|
||||
return specialTranslationsJa[englishName] ?? englishName;
|
||||
}
|
||||
return englishName;
|
||||
}
|
||||
|
||||
@@ -828,54 +829,65 @@ String translateItemNameL10n(String itemString) {
|
||||
|
||||
/// Act 제목 번역
|
||||
String translateActTitle(String englishTitle) {
|
||||
if (isKoreanLocale)
|
||||
if (isKoreanLocale) {
|
||||
return actTitleTranslationsKo[englishTitle] ?? englishTitle;
|
||||
if (isJapaneseLocale)
|
||||
}
|
||||
if (isJapaneseLocale) {
|
||||
return actTitleTranslationsJa[englishTitle] ?? englishTitle;
|
||||
}
|
||||
return englishTitle;
|
||||
}
|
||||
|
||||
/// Act 보스 이름 번역
|
||||
String translateActBoss(String englishBoss) {
|
||||
if (isKoreanLocale) return actBossTranslationsKo[englishBoss] ?? englishBoss;
|
||||
if (isJapaneseLocale)
|
||||
if (isJapaneseLocale) {
|
||||
return actBossTranslationsJa[englishBoss] ?? englishBoss;
|
||||
}
|
||||
return englishBoss;
|
||||
}
|
||||
|
||||
/// Act 퀘스트 번역
|
||||
String translateActQuest(String englishQuest) {
|
||||
if (isKoreanLocale)
|
||||
if (isKoreanLocale) {
|
||||
return actQuestTranslationsKo[englishQuest] ?? englishQuest;
|
||||
if (isJapaneseLocale)
|
||||
}
|
||||
if (isJapaneseLocale) {
|
||||
return actQuestTranslationsJa[englishQuest] ?? englishQuest;
|
||||
}
|
||||
return englishQuest;
|
||||
}
|
||||
|
||||
/// 시네마틱 텍스트 번역
|
||||
String translateCinematic(String englishText) {
|
||||
if (isKoreanLocale)
|
||||
if (isKoreanLocale) {
|
||||
return cinematicTranslationsKo[englishText] ?? englishText;
|
||||
if (isJapaneseLocale)
|
||||
}
|
||||
if (isJapaneseLocale) {
|
||||
return cinematicTranslationsJa[englishText] ?? englishText;
|
||||
}
|
||||
return englishText;
|
||||
}
|
||||
|
||||
/// 지역 이름 번역
|
||||
String translateLocation(String englishLocation) {
|
||||
if (isKoreanLocale)
|
||||
if (isKoreanLocale) {
|
||||
return locationTranslationsKo[englishLocation] ?? englishLocation;
|
||||
if (isJapaneseLocale)
|
||||
}
|
||||
if (isJapaneseLocale) {
|
||||
return locationTranslationsJa[englishLocation] ?? englishLocation;
|
||||
}
|
||||
return englishLocation;
|
||||
}
|
||||
|
||||
/// 세력/조직 이름 번역
|
||||
String translateFaction(String englishFaction) {
|
||||
if (isKoreanLocale)
|
||||
if (isKoreanLocale) {
|
||||
return factionTranslationsKo[englishFaction] ?? englishFaction;
|
||||
if (isJapaneseLocale)
|
||||
}
|
||||
if (isJapaneseLocale) {
|
||||
return factionTranslationsJa[englishFaction] ?? englishFaction;
|
||||
}
|
||||
return englishFaction;
|
||||
}
|
||||
|
||||
@@ -1233,7 +1245,7 @@ String get notifyQuestComplete => _l('QUEST COMPLETE!', '퀘스트 완료!', '
|
||||
String get notifyPrologueComplete =>
|
||||
_l('PROLOGUE COMPLETE!', '프롤로그 완료!', 'プロローグ完了!');
|
||||
String notifyActComplete(int actNumber) =>
|
||||
_l('ACT $actNumber COMPLETE!', '${actNumber}막 완료!', '第${actNumber}幕完了!');
|
||||
_l('ACT $actNumber COMPLETE!', '$actNumber막 완료!', '第$actNumber幕完了!');
|
||||
String get notifyNewSpell => _l('NEW SPELL!', '새 주문!', '新しい呪文!');
|
||||
String get notifyNewEquipment => _l('NEW EQUIPMENT!', '새 장비!', '新しい装備!');
|
||||
String get notifyBossDefeated => _l('BOSS DEFEATED!', '보스 처치!', 'ボス撃破!');
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||
@@ -190,10 +188,7 @@ class IAPService implements IIAPService {
|
||||
|
||||
/// 구매 상태 저장 (보안 저장소 사용)
|
||||
Future<void> _savePurchaseState(bool purchased) async {
|
||||
await _secureStorage.write(
|
||||
key: _purchaseKey,
|
||||
value: purchased.toString(),
|
||||
);
|
||||
await _secureStorage.write(key: _purchaseKey, value: purchased.toString());
|
||||
_adRemovalPurchased = purchased;
|
||||
debugPrint('[IAPService] Saved purchase state: $purchased');
|
||||
}
|
||||
@@ -451,10 +446,7 @@ class IAPService implements IIAPService {
|
||||
pc.PublicKeyParameter<pc.RSAPublicKey>(publicKey),
|
||||
);
|
||||
|
||||
return signer.verifySignature(
|
||||
dataBytes,
|
||||
pc.RSASignature(signatureBytes),
|
||||
);
|
||||
return signer.verifySignature(dataBytes, pc.RSASignature(signatureBytes));
|
||||
} catch (e) {
|
||||
debugPrint('[IAPService] RSA verification error: $e');
|
||||
return false;
|
||||
@@ -477,10 +469,8 @@ class IAPService implements IIAPService {
|
||||
final rsaParser = ASN1Parser(publicKeyBytes);
|
||||
final rsaSequence = rsaParser.nextObject() as ASN1Sequence;
|
||||
|
||||
final modulus =
|
||||
(rsaSequence.elements![0] as ASN1Integer).integer!;
|
||||
final exponent =
|
||||
(rsaSequence.elements![1] as ASN1Integer).integer!;
|
||||
final modulus = (rsaSequence.elements![0] as ASN1Integer).integer!;
|
||||
final exponent = (rsaSequence.elements![1] as ASN1Integer).integer!;
|
||||
|
||||
return pc.RSAPublicKey(modulus, exponent);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
///
|
||||
/// 유틸리티 함수 모음.
|
||||
/// 이 파일은 분할된 모듈들을 re-export하여 기존 코드 호환성 유지.
|
||||
library;
|
||||
|
||||
// 랜덤/확률 함수
|
||||
export 'package:asciineverdie/src/core/util/pq_random.dart';
|
||||
|
||||
@@ -52,7 +52,7 @@ class SavePickerDialog extends StatelessWidget {
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: saves.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final save = saves[index];
|
||||
return _SaveListTile(
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||
import 'package:asciineverdie/l10n/app_localizations.dart';
|
||||
import 'package:asciineverdie/src/shared/l10n/game_data_l10n.dart';
|
||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||
import 'package:asciineverdie/src/core/model/inventory.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/combat_log.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/desktop_panel_widgets.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/equipment_stats_panel.dart';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
///
|
||||
/// CanvasBattleComposer에서 분리된 전투 텍스트 프레임 상수.
|
||||
/// 크리티컬, 회피, 미스, 디버프, DOT, 블록, 패리 텍스트 프레임.
|
||||
library;
|
||||
|
||||
// ============================================================================
|
||||
// 몬스터 공격 이펙트 (← 방향, Phase 8) - 5줄
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:asciineverdie/src/core/engine/death_handler.dart';
|
||||
import 'package:asciineverdie/src/core/model/equipment_container.dart';
|
||||
import 'package:asciineverdie/src/core/model/equipment_item.dart';
|
||||
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
|
||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||
@@ -13,7 +12,7 @@ void main() {
|
||||
const handler = DeathHandler();
|
||||
|
||||
/// 테스트용 장비 생성 헬퍼
|
||||
EquipmentItem _makeItem(String name, EquipmentSlot slot) {
|
||||
EquipmentItem makeItem(String name, EquipmentSlot slot) {
|
||||
return EquipmentItem(
|
||||
name: name,
|
||||
slot: slot,
|
||||
@@ -25,20 +24,20 @@ void main() {
|
||||
}
|
||||
|
||||
/// 모든 슬롯에 장비가 장착된 Equipment 생성
|
||||
Equipment _fullyEquipped() {
|
||||
Equipment fullyEquipped() {
|
||||
return Equipment(
|
||||
items: [
|
||||
EquipmentItem.defaultWeapon(), // 0: 무기(weapon)
|
||||
_makeItem('Iron Shield', EquipmentSlot.shield),
|
||||
_makeItem('Iron Helm', EquipmentSlot.helm),
|
||||
_makeItem('Iron Hauberk', EquipmentSlot.hauberk),
|
||||
_makeItem('Iron Brassairts', EquipmentSlot.brassairts),
|
||||
_makeItem('Iron Vambraces', EquipmentSlot.vambraces),
|
||||
_makeItem('Iron Gauntlets', EquipmentSlot.gauntlets),
|
||||
_makeItem('Iron Gambeson', EquipmentSlot.gambeson),
|
||||
_makeItem('Iron Cuisses', EquipmentSlot.cuisses),
|
||||
_makeItem('Iron Greaves', EquipmentSlot.greaves),
|
||||
_makeItem('Iron Sollerets', EquipmentSlot.sollerets),
|
||||
makeItem('Iron Shield', EquipmentSlot.shield),
|
||||
makeItem('Iron Helm', EquipmentSlot.helm),
|
||||
makeItem('Iron Hauberk', EquipmentSlot.hauberk),
|
||||
makeItem('Iron Brassairts', EquipmentSlot.brassairts),
|
||||
makeItem('Iron Vambraces', EquipmentSlot.vambraces),
|
||||
makeItem('Iron Gauntlets', EquipmentSlot.gauntlets),
|
||||
makeItem('Iron Gambeson', EquipmentSlot.gambeson),
|
||||
makeItem('Iron Cuisses', EquipmentSlot.cuisses),
|
||||
makeItem('Iron Greaves', EquipmentSlot.greaves),
|
||||
makeItem('Iron Sollerets', EquipmentSlot.sollerets),
|
||||
],
|
||||
bestIndex: 0,
|
||||
);
|
||||
@@ -50,7 +49,7 @@ void main() {
|
||||
/// [level]: 캐릭터 레벨
|
||||
/// [isBossFight]: 보스전(boss fight) 여부
|
||||
/// [equipment]: 커스텀 장비
|
||||
GameState _createState({
|
||||
GameState createState({
|
||||
int seed = 42,
|
||||
int level = 1,
|
||||
bool isBossFight = false,
|
||||
@@ -59,7 +58,7 @@ void main() {
|
||||
final state = GameState(
|
||||
rng: DeterministicRandom(seed),
|
||||
traits: Traits.empty().copyWith(level: level),
|
||||
equipment: equipment ?? _fullyEquipped(),
|
||||
equipment: equipment ?? fullyEquipped(),
|
||||
progress: ProgressState.empty().copyWith(
|
||||
currentCombat: MockFactories.createCombat(),
|
||||
finalBossState: isBossFight
|
||||
@@ -75,7 +74,7 @@ void main() {
|
||||
group('deathInfo 생성', () {
|
||||
test('사망 시 deathInfo가 올바르게 생성된다', () {
|
||||
// 준비(arrange): Lv10 캐릭터 - 100% 장비 손실 확률
|
||||
final state = _createState(level: 10);
|
||||
final state = createState(level: 10);
|
||||
|
||||
// 실행(act)
|
||||
final result = handler.processPlayerDeath(
|
||||
@@ -94,7 +93,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('selfDamage 원인으로 사망 시 cause가 올바르다', () {
|
||||
final state = _createState(level: 1);
|
||||
final state = createState(level: 1);
|
||||
|
||||
final result = handler.processPlayerDeath(
|
||||
state,
|
||||
@@ -108,8 +107,8 @@ void main() {
|
||||
|
||||
group('보스전(boss fight) 사망', () {
|
||||
test('보스전 사망 시 장비가 보호된다 (lostCount == 0)', () {
|
||||
final equipment = _fullyEquipped();
|
||||
final state = _createState(
|
||||
final equipment = fullyEquipped();
|
||||
final state = createState(
|
||||
level: 10,
|
||||
isBossFight: true,
|
||||
equipment: equipment,
|
||||
@@ -136,7 +135,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('보스전 사망 시 bossLevelingEndTime이 설정된다 (5분)', () {
|
||||
final state = _createState(isBossFight: true);
|
||||
final state = createState(isBossFight: true);
|
||||
final before = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
final result = handler.processPlayerDeath(
|
||||
@@ -157,7 +156,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('일반 사망 시 bossLevelingEndTime은 null이다', () {
|
||||
final state = _createState(level: 1);
|
||||
final state = createState(level: 1);
|
||||
|
||||
final result = handler.processPlayerDeath(
|
||||
state,
|
||||
@@ -185,7 +184,7 @@ void main() {
|
||||
}
|
||||
expect(lossySeed, isNotNull, reason: '장비 손실 시드를 찾을 수 없음');
|
||||
|
||||
final state = _createState(seed: lossySeed!, level: 1);
|
||||
final state = createState(seed: lossySeed!, level: 1);
|
||||
final result = handler.processPlayerDeath(
|
||||
state,
|
||||
killerName: 'Goblin',
|
||||
@@ -208,7 +207,7 @@ void main() {
|
||||
}
|
||||
expect(safeSeed, isNotNull, reason: '장비 보호 시드를 찾을 수 없음');
|
||||
|
||||
final state = _createState(seed: safeSeed!, level: 1);
|
||||
final state = createState(seed: safeSeed!, level: 1);
|
||||
final result = handler.processPlayerDeath(
|
||||
state,
|
||||
killerName: 'Goblin',
|
||||
@@ -221,7 +220,7 @@ void main() {
|
||||
|
||||
test('Lv10+: 100% 확률로 장비 손실', () {
|
||||
// Lv10 이상은 항상 100% 손실
|
||||
final state = _createState(seed: 42, level: 10);
|
||||
final state = createState(seed: 42, level: 10);
|
||||
|
||||
final result = handler.processPlayerDeath(
|
||||
state,
|
||||
@@ -249,7 +248,7 @@ void main() {
|
||||
// Lv10(100% 손실)으로 여러 시드를 반복 테스트
|
||||
// 무기는 절대 사라지지 않아야 함
|
||||
for (var s = 0; s < 50; s++) {
|
||||
final state = _createState(seed: s, level: 10);
|
||||
final state = createState(seed: s, level: 10);
|
||||
final result = handler.processPlayerDeath(
|
||||
state,
|
||||
killerName: 'Monster',
|
||||
@@ -280,7 +279,7 @@ void main() {
|
||||
// 무기만 있고 나머지는 빈 장비
|
||||
final emptyEquipment = Equipment.empty();
|
||||
// Equipment.empty()는 기본 무기(Keyboard)만 있음
|
||||
final state = _createState(
|
||||
final state = createState(
|
||||
seed: 42,
|
||||
level: 10, // 100% 손실 확률
|
||||
equipment: emptyEquipment,
|
||||
@@ -300,7 +299,7 @@ void main() {
|
||||
|
||||
group('deathCount 증가', () {
|
||||
test('사망 시 deathCount가 1 증가한다', () {
|
||||
final state = _createState();
|
||||
final state = createState();
|
||||
expect(state.progress.deathCount, 0);
|
||||
|
||||
final result = handler.processPlayerDeath(
|
||||
@@ -314,7 +313,7 @@ void main() {
|
||||
|
||||
test('연속 사망 시 deathCount가 누적된다', () {
|
||||
// 첫 번째 사망 (deathCount: 0 -> 1)
|
||||
var state = _createState(seed: 100);
|
||||
var state = createState(seed: 100);
|
||||
state = handler.processPlayerDeath(
|
||||
state,
|
||||
killerName: 'Goblin',
|
||||
@@ -338,7 +337,7 @@ void main() {
|
||||
group('currentCombat 초기화', () {
|
||||
test('사망 후 currentCombat이 null로 초기화되어야 한다', () {
|
||||
// 전투 중(combat active) 상태에서 사망
|
||||
final state = _createState();
|
||||
final state = createState();
|
||||
expect(state.progress.currentCombat, isNotNull);
|
||||
|
||||
final result = handler.processPlayerDeath(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -7,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
/// 다양한 GCD 값에 대해 전투 효율성을 측정
|
||||
void main() {
|
||||
test('GCD 시뮬레이션 - 다양한 값 비교', () {
|
||||
print('\n' + '=' * 80);
|
||||
print('\n${'=' * 80}');
|
||||
print('글로벌 쿨타임(GCD) 시뮬레이션 결과');
|
||||
print('=' * 80);
|
||||
|
||||
@@ -93,14 +95,14 @@ void main() {
|
||||
'- DPS 손실이 크지 않음 (${((1 - gcd1500.avgDps / baselineDps) * 100).toStringAsFixed(0)}% ~ ${((1 - gcd2000.avgDps / baselineDps) * 100).toStringAsFixed(0)}%)',
|
||||
);
|
||||
|
||||
print('\n' + '=' * 80);
|
||||
print('\n${'=' * 80}');
|
||||
|
||||
// 테스트는 항상 통과 (정보 출력용)
|
||||
expect(true, isTrue);
|
||||
});
|
||||
|
||||
test('GCD 상세 시뮬레이션 - 레벨별 영향', () {
|
||||
print('\n' + '=' * 80);
|
||||
print('\n${'=' * 80}');
|
||||
print('레벨별 GCD 영향 분석');
|
||||
print('=' * 80);
|
||||
|
||||
@@ -153,7 +155,7 @@ void main() {
|
||||
}
|
||||
}
|
||||
|
||||
print('\n' + '=' * 80);
|
||||
print('\n${'=' * 80}');
|
||||
expect(true, isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// ignore_for_file: inference_failure_on_collection_literal
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:asciineverdie/src/core/model/equipment_container.dart';
|
||||
import 'package:asciineverdie/src/core/model/equipment_item.dart';
|
||||
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
|
||||
import 'package:asciineverdie/src/core/model/item_stats.dart';
|
||||
@@ -17,7 +18,7 @@ void main() {
|
||||
// 헬퍼: 모든 필드가 채워진 GameSave 생성
|
||||
// =========================================================================
|
||||
|
||||
GameSave _createFullSave() {
|
||||
GameSave createFullSave() {
|
||||
return GameSave(
|
||||
version: kSaveVersion,
|
||||
rngState: 12345,
|
||||
@@ -144,10 +145,10 @@ void main() {
|
||||
// =========================================================================
|
||||
|
||||
test('toJson() → fromJson() 라운드트립 — 모든 필드 보존', () {
|
||||
final original = _createFullSave();
|
||||
final original = createFullSave();
|
||||
// jsonEncode → jsonDecode로 순수 Map 변환 (freezed 객체 제거)
|
||||
final json = jsonDecode(jsonEncode(original.toJson()))
|
||||
as Map<String, dynamic>;
|
||||
final json =
|
||||
jsonDecode(jsonEncode(original.toJson())) as Map<String, dynamic>;
|
||||
final restored = GameSave.fromJson(json);
|
||||
|
||||
// traits 검증
|
||||
@@ -184,10 +185,7 @@ void main() {
|
||||
expect(restored.equipment.weapon, equals('Flaming Sword'));
|
||||
expect(restored.equipment.shield, equals('Tower Shield'));
|
||||
expect(restored.equipment.bestIndex, equals(0));
|
||||
expect(
|
||||
restored.equipment.weaponItem.rarity,
|
||||
equals(ItemRarity.epic),
|
||||
);
|
||||
expect(restored.equipment.weaponItem.rarity, equals(ItemRarity.epic));
|
||||
expect(restored.equipment.weaponItem.level, equals(10));
|
||||
expect(restored.equipment.weaponItem.stats.atk, equals(50));
|
||||
|
||||
@@ -201,7 +199,10 @@ void main() {
|
||||
expect(restored.progress.task.max, equals(100));
|
||||
expect(restored.progress.quest.position, equals(3));
|
||||
expect(restored.progress.exp.position, equals(500));
|
||||
expect(restored.progress.currentTask.caption, equals('Executing a Goblin'));
|
||||
expect(
|
||||
restored.progress.currentTask.caption,
|
||||
equals('Executing a Goblin'),
|
||||
);
|
||||
expect(restored.progress.currentTask.type, equals(TaskType.kill));
|
||||
expect(
|
||||
restored.progress.currentTask.monsterGrade,
|
||||
@@ -252,7 +253,14 @@ void main() {
|
||||
'version': 2,
|
||||
'rng': 100,
|
||||
'traits': {'name': 'OldHero', 'race': 'Elf', 'klass': 'Mage'},
|
||||
'stats': {'str': 10, 'con': 10, 'dex': 10, 'int': 10, 'wis': 10, 'cha': 10},
|
||||
'stats': {
|
||||
'str': 10,
|
||||
'con': 10,
|
||||
'dex': 10,
|
||||
'int': 10,
|
||||
'wis': 10,
|
||||
'cha': 10,
|
||||
},
|
||||
'inventory': {'gold': 500, 'items': []},
|
||||
'equipment': {
|
||||
'weapon': 'Ancient Sword',
|
||||
@@ -291,7 +299,10 @@ void main() {
|
||||
// 레거시 아이템은 level 1, common으로 변환
|
||||
expect(restored.equipment.weaponItem.level, equals(1));
|
||||
expect(restored.equipment.weaponItem.rarity, equals(ItemRarity.common));
|
||||
expect(restored.equipment.weaponItem.slot, equals(EquipmentSlot.weapon));
|
||||
expect(
|
||||
restored.equipment.weaponItem.slot,
|
||||
equals(EquipmentSlot.weapon),
|
||||
);
|
||||
|
||||
// 버전 정보 보존
|
||||
expect(restored.version, equals(2));
|
||||
@@ -299,7 +310,9 @@ void main() {
|
||||
|
||||
test('v2 세이브에 monetization 없으면 null', () {
|
||||
// jsonDecode로 순수 Map<String, dynamic> 생성 (타입 캐스팅 호환)
|
||||
final legacyJson = jsonDecode(jsonEncode({
|
||||
final legacyJson =
|
||||
jsonDecode(
|
||||
jsonEncode({
|
||||
'version': 2,
|
||||
'rng': 0,
|
||||
'traits': {},
|
||||
@@ -322,7 +335,9 @@ void main() {
|
||||
'skills': [],
|
||||
'progress': {},
|
||||
'queue': [],
|
||||
})) as Map<String, dynamic>;
|
||||
}),
|
||||
)
|
||||
as Map<String, dynamic>;
|
||||
|
||||
final restored = GameSave.fromJson(legacyJson);
|
||||
|
||||
@@ -404,10 +419,7 @@ void main() {
|
||||
'plot': {'pos': 0, 'max': 1},
|
||||
'exp': {'pos': 0, 'max': 1},
|
||||
'encumbrance': {'pos': 0, 'max': 1},
|
||||
'taskInfo': {
|
||||
'caption': 'Unknown task',
|
||||
'type': 'nonexistent_type',
|
||||
},
|
||||
'taskInfo': {'caption': 'Unknown task', 'type': 'nonexistent_type'},
|
||||
},
|
||||
'queue': [
|
||||
{
|
||||
@@ -424,10 +436,7 @@ void main() {
|
||||
// 알 수 없는 enum 값은 기본값으로 대체
|
||||
expect(restored.progress.currentTask.type, equals(TaskType.neutral));
|
||||
expect(restored.queue.entries.first.kind, equals(QueueKind.task));
|
||||
expect(
|
||||
restored.queue.entries.first.taskType,
|
||||
equals(TaskType.neutral),
|
||||
);
|
||||
expect(restored.queue.entries.first.taskType, equals(TaskType.neutral));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -448,9 +457,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('skills가 빈 배열이면 빈 스킬북', () {
|
||||
final json = <String, dynamic>{
|
||||
'skills': [],
|
||||
};
|
||||
final json = <String, dynamic>{'skills': []};
|
||||
|
||||
final restored = GameSave.fromJson(json);
|
||||
|
||||
@@ -494,9 +501,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('queue가 빈 배열이면 빈 큐', () {
|
||||
final json = <String, dynamic>{
|
||||
'queue': [],
|
||||
};
|
||||
final json = <String, dynamic>{'queue': []};
|
||||
|
||||
final restored = GameSave.fromJson(json);
|
||||
|
||||
@@ -504,9 +509,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('monetization null이면 null 유지', () {
|
||||
final json = <String, dynamic>{
|
||||
'monetization': null,
|
||||
};
|
||||
final json = <String, dynamic>{'monetization': null};
|
||||
|
||||
final restored = GameSave.fromJson(json);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user