Compare commits
3 Commits
1da377c127
...
f9a4ae105a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9a4ae105a | ||
|
|
81eb2f8463 | ||
|
|
eba0521ffe |
@@ -89,19 +89,11 @@ class CanvasWalkingComposer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// idle 프레임 기반 걷기 애니메이션 생성
|
/// idle 프레임 기반 걷기 애니메이션 생성
|
||||||
/// 머리와 몸통은 유지, 다리만 걷는 동작으로 변경
|
/// 종족별 다리 모양을 유지 (idle 프레임이 4개라 자연스럽게 변화)
|
||||||
List<String> _animateWalking(List<String> idleLines, int frameIndex) {
|
List<String> _animateWalking(List<String> idleLines, int frameIndex) {
|
||||||
if (idleLines.length < 3) return idleLines;
|
// idle 프레임을 그대로 사용 (종족별 다리 모양 유지)
|
||||||
|
// frameIndex에 따라 idle[0~3] 중 하나가 선택되어 자연스럽게 애니메이션됨
|
||||||
// 머리(0)와 몸통(1)은 그대로 유지
|
return idleLines;
|
||||||
final head = idleLines[0];
|
|
||||||
final body = idleLines[1];
|
|
||||||
|
|
||||||
// 다리 애니메이션 (4프레임) - 걷기 동작
|
|
||||||
const legFrames = [' /| ', ' |\\ ', ' /| ', ' |\\ '];
|
|
||||||
final legs = legFrames[frameIndex % legFrames.length];
|
|
||||||
|
|
||||||
return [head, body, legs];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 문자열 스프라이트를 AsciiCell 2D 배열로 변환
|
/// 문자열 스프라이트를 AsciiCell 2D 배열로 변환
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:asciineverdie/src/core/model/equipment_item.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/monster_grade.dart';
|
||||||
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
||||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||||
|
|
||||||
const int kSaveVersion = 2;
|
/// 세이브 파일 버전
|
||||||
|
/// - v2: 장비 이름만 저장 (레거시)
|
||||||
|
/// - v3: 장비 전체 정보 저장 (level, rarity, stats 포함)
|
||||||
|
const int kSaveVersion = 3;
|
||||||
|
|
||||||
class GameSave {
|
class GameSave {
|
||||||
GameSave({
|
GameSave({
|
||||||
@@ -79,17 +85,7 @@ class GameSave {
|
|||||||
.toList(),
|
.toList(),
|
||||||
},
|
},
|
||||||
'equipment': {
|
'equipment': {
|
||||||
'weapon': equipment.weapon,
|
'items': equipment.items.map((e) => e.toJson()).toList(),
|
||||||
'shield': equipment.shield,
|
|
||||||
'helm': equipment.helm,
|
|
||||||
'hauberk': equipment.hauberk,
|
|
||||||
'brassairts': equipment.brassairts,
|
|
||||||
'vambraces': equipment.vambraces,
|
|
||||||
'gauntlets': equipment.gauntlets,
|
|
||||||
'gambeson': equipment.gambeson,
|
|
||||||
'cuisses': equipment.cuisses,
|
|
||||||
'greaves': equipment.greaves,
|
|
||||||
'sollerets': equipment.sollerets,
|
|
||||||
'bestIndex': equipment.bestIndex,
|
'bestIndex': equipment.bestIndex,
|
||||||
},
|
},
|
||||||
'skills': skillBook.skills
|
'skills': skillBook.skills
|
||||||
@@ -106,6 +102,8 @@ class GameSave {
|
|||||||
'type': progress.currentTask.type.name,
|
'type': progress.currentTask.type.name,
|
||||||
'monsterBaseName': progress.currentTask.monsterBaseName,
|
'monsterBaseName': progress.currentTask.monsterBaseName,
|
||||||
'monsterPart': progress.currentTask.monsterPart,
|
'monsterPart': progress.currentTask.monsterPart,
|
||||||
|
'monsterLevel': progress.currentTask.monsterLevel,
|
||||||
|
'monsterGrade': progress.currentTask.monsterGrade?.name,
|
||||||
},
|
},
|
||||||
'plotStages': progress.plotStageCount,
|
'plotStages': progress.plotStageCount,
|
||||||
'questCount': progress.questCount,
|
'questCount': progress.questCount,
|
||||||
@@ -183,20 +181,7 @@ class GameSave {
|
|||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
equipment: Equipment.fromStrings(
|
equipment: _equipmentFromJson(equipmentJson, json['version'] as int? ?? 2),
|
||||||
weapon: equipmentJson['weapon'] as String? ?? 'Keyboard',
|
|
||||||
shield: equipmentJson['shield'] as String? ?? '',
|
|
||||||
helm: equipmentJson['helm'] as String? ?? '',
|
|
||||||
hauberk: equipmentJson['hauberk'] as String? ?? '',
|
|
||||||
brassairts: equipmentJson['brassairts'] as String? ?? '',
|
|
||||||
vambraces: equipmentJson['vambraces'] as String? ?? '',
|
|
||||||
gauntlets: equipmentJson['gauntlets'] as String? ?? '',
|
|
||||||
gambeson: equipmentJson['gambeson'] as String? ?? '',
|
|
||||||
cuisses: equipmentJson['cuisses'] as String? ?? '',
|
|
||||||
greaves: equipmentJson['greaves'] as String? ?? '',
|
|
||||||
sollerets: equipmentJson['sollerets'] as String? ?? '',
|
|
||||||
bestIndex: equipmentJson['bestIndex'] as int? ?? 0,
|
|
||||||
),
|
|
||||||
skillBook: SkillBook(
|
skillBook: SkillBook(
|
||||||
skills: skillsJson
|
skills: skillsJson
|
||||||
.map(
|
.map(
|
||||||
@@ -289,11 +274,23 @@ TaskInfo _taskInfoFromJson(Map<String, dynamic> json) {
|
|||||||
(t) => t.name == typeName,
|
(t) => t.name == typeName,
|
||||||
orElse: () => TaskType.neutral,
|
orElse: () => TaskType.neutral,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// monsterGrade 파싱
|
||||||
|
final gradeName = json['monsterGrade'] as String?;
|
||||||
|
final monsterGrade = gradeName != null
|
||||||
|
? MonsterGrade.values.firstWhere(
|
||||||
|
(g) => g.name == gradeName,
|
||||||
|
orElse: () => MonsterGrade.normal,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
return TaskInfo(
|
return TaskInfo(
|
||||||
caption: json['caption'] as String? ?? '',
|
caption: json['caption'] as String? ?? '',
|
||||||
type: type,
|
type: type,
|
||||||
monsterBaseName: json['monsterBaseName'] as String?,
|
monsterBaseName: json['monsterBaseName'] as String?,
|
||||||
monsterPart: json['monsterPart'] as String?,
|
monsterPart: json['monsterPart'] as String?,
|
||||||
|
monsterLevel: json['monsterLevel'] as int?,
|
||||||
|
monsterGrade: monsterGrade,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,3 +312,46 @@ QuestMonsterInfo? _questMonsterFromJson(Map<String, dynamic>? json) {
|
|||||||
monsterIndex: json['index'] as int? ?? -1,
|
monsterIndex: json['index'] as int? ?? -1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 장비 데이터 역직렬화 (버전별 분기 처리)
|
||||||
|
///
|
||||||
|
/// - v2 이하: 레거시 문자열 기반 (이름만 저장)
|
||||||
|
/// - v3 이상: 전체 EquipmentItem 정보 저장
|
||||||
|
Equipment _equipmentFromJson(Map<String, dynamic> json, int version) {
|
||||||
|
// v3 이상: 새로운 형식 (items 배열)
|
||||||
|
if (version >= 3 && json['items'] != null) {
|
||||||
|
final itemsList = json['items'] as List<dynamic>;
|
||||||
|
final items = <EquipmentItem>[];
|
||||||
|
|
||||||
|
for (var i = 0; i < Equipment.slotCount; i++) {
|
||||||
|
if (i < itemsList.length) {
|
||||||
|
final itemJson = itemsList[i] as Map<String, dynamic>;
|
||||||
|
items.add(EquipmentItem.fromJson(itemJson));
|
||||||
|
} else {
|
||||||
|
// 누락된 슬롯은 빈 아이템으로 채움
|
||||||
|
items.add(EquipmentItem.empty(EquipmentSlot.values[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Equipment(
|
||||||
|
items: items,
|
||||||
|
bestIndex: json['bestIndex'] as int? ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v2 이하: 레거시 형식 (문자열 기반)
|
||||||
|
return Equipment.fromStrings(
|
||||||
|
weapon: json['weapon'] as String? ?? 'Keyboard',
|
||||||
|
shield: json['shield'] as String? ?? '',
|
||||||
|
helm: json['helm'] as String? ?? '',
|
||||||
|
hauberk: json['hauberk'] as String? ?? '',
|
||||||
|
brassairts: json['brassairts'] as String? ?? '',
|
||||||
|
vambraces: json['vambraces'] as String? ?? '',
|
||||||
|
gauntlets: json['gauntlets'] as String? ?? '',
|
||||||
|
gambeson: json['gambeson'] as String? ?? '',
|
||||||
|
cuisses: json['cuisses'] as String? ?? '',
|
||||||
|
greaves: json['greaves'] as String? ?? '',
|
||||||
|
sollerets: json['sollerets'] as String? ?? '',
|
||||||
|
bestIndex: json['bestIndex'] as int? ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:asciineverdie/src/core/engine/item_service.dart';
|
|||||||
import 'package:asciineverdie/src/core/model/arena_match.dart';
|
import 'package:asciineverdie/src/core/model/arena_match.dart';
|
||||||
import 'package:asciineverdie/src/core/model/equipment_item.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/equipment_slot.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/hall_of_fame.dart';
|
||||||
import 'package:asciineverdie/src/core/model/item_stats.dart';
|
import 'package:asciineverdie/src/core/model/item_stats.dart';
|
||||||
import 'package:asciineverdie/src/features/game/widgets/combat_log.dart';
|
import 'package:asciineverdie/src/features/game/widgets/combat_log.dart';
|
||||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||||
@@ -103,6 +104,9 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
|||||||
final fileName = 'arena_${challenger}_vs_${opponent}_$timestamp.json';
|
final fileName = 'arena_${challenger}_vs_${opponent}_$timestamp.json';
|
||||||
final file = File('${directory.path}/$fileName');
|
final file = File('${directory.path}/$fileName');
|
||||||
|
|
||||||
|
// 전투 통계 계산
|
||||||
|
final stats = _calculateBattleStats();
|
||||||
|
|
||||||
final jsonData = {
|
final jsonData = {
|
||||||
'match': {
|
'match': {
|
||||||
'challenger': challenger,
|
'challenger': challenger,
|
||||||
@@ -111,6 +115,11 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
|||||||
'turnCount': widget.turnCount,
|
'turnCount': widget.turnCount,
|
||||||
'timestamp': DateTime.now().toIso8601String(),
|
'timestamp': DateTime.now().toIso8601String(),
|
||||||
},
|
},
|
||||||
|
'characters': {
|
||||||
|
'challenger': _characterToJson(widget.result.match.challenger),
|
||||||
|
'opponent': _characterToJson(widget.result.match.opponent),
|
||||||
|
},
|
||||||
|
'stats': stats,
|
||||||
'battleLog': widget.battleLog!.map((e) => e.toJson()).toList(),
|
'battleLog': widget.battleLog!.map((e) => e.toJson()).toList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,6 +155,126 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 캐릭터 정보를 JSON으로 변환
|
||||||
|
Map<String, dynamic> _characterToJson(HallOfFameEntry entry) {
|
||||||
|
return {
|
||||||
|
'name': entry.characterName,
|
||||||
|
'level': entry.level,
|
||||||
|
'race': entry.race,
|
||||||
|
'class': entry.klass,
|
||||||
|
'combatStats': entry.finalStats?.toJson(),
|
||||||
|
'equipment': entry.finalEquipment
|
||||||
|
?.map((EquipmentItem e) => {
|
||||||
|
'slot': e.slot.name,
|
||||||
|
'name': e.name,
|
||||||
|
'level': e.level,
|
||||||
|
'rarity': e.rarity.name,
|
||||||
|
'stats': e.stats.toJson(),
|
||||||
|
})
|
||||||
|
.toList(),
|
||||||
|
'skills': entry.finalSkills,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 배틀 로그에서 전투 통계 계산
|
||||||
|
Map<String, dynamic> _calculateBattleStats() {
|
||||||
|
if (widget.battleLog == null || widget.battleLog!.isEmpty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int challengerTotalDamage = 0;
|
||||||
|
int opponentTotalDamage = 0;
|
||||||
|
int challengerTotalHeal = 0;
|
||||||
|
int opponentTotalHeal = 0;
|
||||||
|
int challengerCriticals = 0;
|
||||||
|
int opponentCriticals = 0;
|
||||||
|
int challengerBlocks = 0;
|
||||||
|
int opponentBlocks = 0;
|
||||||
|
int challengerEvades = 0;
|
||||||
|
int opponentEvades = 0;
|
||||||
|
int challengerSkillsUsed = 0;
|
||||||
|
int opponentSkillsUsed = 0;
|
||||||
|
|
||||||
|
final challenger = widget.result.match.challenger.characterName;
|
||||||
|
|
||||||
|
for (final entry in widget.battleLog!) {
|
||||||
|
final msg = entry.message;
|
||||||
|
final isChallenger = msg.startsWith(challenger);
|
||||||
|
|
||||||
|
switch (entry.type) {
|
||||||
|
case CombatLogType.damage:
|
||||||
|
final dmg = _extractNumber(msg);
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerTotalDamage += dmg;
|
||||||
|
}
|
||||||
|
case CombatLogType.monsterAttack:
|
||||||
|
final dmg = _extractNumber(msg);
|
||||||
|
opponentTotalDamage += dmg;
|
||||||
|
case CombatLogType.critical:
|
||||||
|
final dmg = _extractNumber(msg);
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerTotalDamage += dmg;
|
||||||
|
challengerCriticals++;
|
||||||
|
} else {
|
||||||
|
opponentTotalDamage += dmg;
|
||||||
|
opponentCriticals++;
|
||||||
|
}
|
||||||
|
case CombatLogType.heal:
|
||||||
|
final heal = _extractNumber(msg);
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerTotalHeal += heal;
|
||||||
|
} else {
|
||||||
|
opponentTotalHeal += heal;
|
||||||
|
}
|
||||||
|
case CombatLogType.block:
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerBlocks++;
|
||||||
|
} else {
|
||||||
|
opponentBlocks++;
|
||||||
|
}
|
||||||
|
case CombatLogType.evade:
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerEvades++;
|
||||||
|
} else {
|
||||||
|
opponentEvades++;
|
||||||
|
}
|
||||||
|
case CombatLogType.skill:
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerSkillsUsed++;
|
||||||
|
} else {
|
||||||
|
opponentSkillsUsed++;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'challenger': {
|
||||||
|
'totalDamage': challengerTotalDamage,
|
||||||
|
'totalHeal': challengerTotalHeal,
|
||||||
|
'criticals': challengerCriticals,
|
||||||
|
'blocks': challengerBlocks,
|
||||||
|
'evades': challengerEvades,
|
||||||
|
'skillsUsed': challengerSkillsUsed,
|
||||||
|
},
|
||||||
|
'opponent': {
|
||||||
|
'totalDamage': opponentTotalDamage,
|
||||||
|
'totalHeal': opponentTotalHeal,
|
||||||
|
'criticals': opponentCriticals,
|
||||||
|
'blocks': opponentBlocks,
|
||||||
|
'evades': opponentEvades,
|
||||||
|
'skillsUsed': opponentSkillsUsed,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 메시지에서 숫자 추출
|
||||||
|
int _extractNumber(String msg) {
|
||||||
|
final match = RegExp(r'(\d+)').firstMatch(msg);
|
||||||
|
return match != null ? int.tryParse(match.group(1)!) ?? 0 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isVictory = widget.result.isVictory;
|
final isVictory = widget.result.isVictory;
|
||||||
|
|||||||
@@ -226,13 +226,11 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_showDeathAnimation = true;
|
_showDeathAnimation = true;
|
||||||
});
|
});
|
||||||
return; // 사망 애니메이션 중에는 다른 업데이트 무시
|
// 분해 애니메이션은 오버레이로 표시되므로
|
||||||
|
// 백그라운드 상태 업데이트는 계속 진행 (20배속 대응)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사망 애니메이션 중에는 다른 업데이트 무시
|
|
||||||
if (_showDeathAnimation) return;
|
|
||||||
|
|
||||||
// 전투 이벤트 동기화 (Phase 5)
|
// 전투 이벤트 동기화 (Phase 5)
|
||||||
if (widget.latestCombatEvent != null &&
|
if (widget.latestCombatEvent != null &&
|
||||||
widget.latestCombatEvent!.timestamp != _lastEventTimestamp) {
|
widget.latestCombatEvent!.timestamp != _lastEventTimestamp) {
|
||||||
@@ -253,7 +251,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
oldWidget.weaponRarity != widget.weaponRarity ||
|
oldWidget.weaponRarity != widget.weaponRarity ||
|
||||||
oldWidget.opponentRaceId != widget.opponentRaceId ||
|
oldWidget.opponentRaceId != widget.opponentRaceId ||
|
||||||
oldWidget.opponentHasShield != widget.opponentHasShield ||
|
oldWidget.opponentHasShield != widget.opponentHasShield ||
|
||||||
oldWidget.isInCombat != widget.isInCombat) {
|
oldWidget.isInCombat != widget.isInCombat ||
|
||||||
|
oldWidget.monsterDied != widget.monsterDied) {
|
||||||
_updateAnimation();
|
_updateAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
/// 테스트용 MaterialApp 래퍼 (localization 포함)
|
/// 테스트용 MaterialApp 래퍼 (localization 포함)
|
||||||
|
/// locale을 영어로 고정하여 테스트 텍스트와 일치시킴
|
||||||
Widget _buildTestApp(Widget child) {
|
Widget _buildTestApp(Widget child) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
localizationsDelegates: L10n.localizationsDelegates,
|
localizationsDelegates: L10n.localizationsDelegates,
|
||||||
supportedLocales: L10n.supportedLocales,
|
supportedLocales: L10n.supportedLocales,
|
||||||
|
locale: const Locale('en'), // 영어 locale 고정
|
||||||
home: child,
|
home: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -20,23 +22,25 @@ void main() {
|
|||||||
NewCharacterScreen(onCharacterCreated: (_, {bool testMode = false}) {}),
|
NewCharacterScreen(onCharacterCreated: (_, {bool testMode = false}) {}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// Localization 로드 대기
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// 화면 타이틀 확인 (l10n 적용됨)
|
// 화면 타이틀 확인 (l10n 적용됨)
|
||||||
expect(find.text('ASCII NEVER DIE - New Character'), findsOneWidget);
|
expect(find.text('ASCII NEVER DIE - NEW CHARACTER'), findsOneWidget);
|
||||||
|
|
||||||
// 종족 섹션 확인
|
// 종족 섹션 확인 (대문자 하드코딩)
|
||||||
expect(find.text('Race'), findsOneWidget);
|
expect(find.text('RACE'), findsOneWidget);
|
||||||
|
|
||||||
// 직업 섹션 확인
|
// 직업 섹션 확인 (대문자 하드코딩)
|
||||||
expect(find.text('Class'), findsOneWidget);
|
expect(find.text('CLASS'), findsOneWidget);
|
||||||
|
|
||||||
// 능력치 섹션 확인
|
// 능력치 섹션 확인 (대문자 하드코딩)
|
||||||
expect(find.text('Stats'), findsOneWidget);
|
expect(find.text('STATS'), findsOneWidget);
|
||||||
expect(find.text('STR'), findsOneWidget);
|
expect(find.text('STR'), findsOneWidget);
|
||||||
expect(find.text('CON'), findsOneWidget);
|
expect(find.text('CON'), findsOneWidget);
|
||||||
|
|
||||||
// Sold! 버튼 확인
|
// Sold! 버튼 확인
|
||||||
expect(find.text('Sold!'), findsOneWidget);
|
expect(find.text('SOLD!'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Unroll button exists and can be tapped', (tester) async {
|
testWidgets('Unroll button exists and can be tapped', (tester) async {
|
||||||
@@ -45,17 +49,18 @@ void main() {
|
|||||||
NewCharacterScreen(onCharacterCreated: (_, {bool testMode = false}) {}),
|
NewCharacterScreen(onCharacterCreated: (_, {bool testMode = false}) {}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Unroll 버튼 확인
|
// Unroll 버튼 확인 (RetroTextButton이 대문자로 변환)
|
||||||
final unrollButton = find.text('Unroll');
|
final unrollButton = find.text('UNROLL');
|
||||||
expect(unrollButton, findsOneWidget);
|
expect(unrollButton, findsOneWidget);
|
||||||
|
|
||||||
// Unroll 버튼 탭
|
// Unroll 버튼 탭
|
||||||
await tester.tap(unrollButton);
|
await tester.tap(unrollButton);
|
||||||
await tester.pump();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Total이 표시되는지 확인
|
// Total이 표시되는지 확인 (TOTAL은 대문자로 표시됨)
|
||||||
expect(find.textContaining('Total'), findsOneWidget);
|
expect(find.textContaining('TOTAL'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Sold button creates character with generated name', (
|
testWidgets('Sold button creates character with generated name', (
|
||||||
@@ -72,17 +77,18 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Sold! 버튼이 보이도록 스크롤
|
// Sold! 버튼이 보이도록 스크롤
|
||||||
await tester.scrollUntilVisible(
|
await tester.scrollUntilVisible(
|
||||||
find.text('Sold!'),
|
find.text('SOLD!'),
|
||||||
500.0,
|
500.0,
|
||||||
scrollable: find.byType(Scrollable).first,
|
scrollable: find.byType(Scrollable).first,
|
||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Sold! 버튼 탭
|
// Sold! 버튼 탭
|
||||||
await tester.tap(find.text('Sold!'));
|
await tester.tap(find.text('SOLD!'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// 콜백이 호출되었는지 확인
|
// 콜백이 호출되었는지 확인
|
||||||
@@ -99,6 +105,7 @@ void main() {
|
|||||||
NewCharacterScreen(onCharacterCreated: (_, {bool testMode = false}) {}),
|
NewCharacterScreen(onCharacterCreated: (_, {bool testMode = false}) {}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// 능력치 라벨들이 표시되는지 확인
|
// 능력치 라벨들이 표시되는지 확인
|
||||||
expect(find.text('STR'), findsOneWidget);
|
expect(find.text('STR'), findsOneWidget);
|
||||||
@@ -108,8 +115,8 @@ void main() {
|
|||||||
expect(find.text('WIS'), findsOneWidget);
|
expect(find.text('WIS'), findsOneWidget);
|
||||||
expect(find.text('CHA'), findsOneWidget);
|
expect(find.text('CHA'), findsOneWidget);
|
||||||
|
|
||||||
// Total 라벨 확인
|
// Total 라벨 확인 (TOTAL은 대문자로 표시됨)
|
||||||
expect(find.textContaining('Total'), findsOneWidget);
|
expect(find.textContaining('TOTAL'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Name text field exists', (tester) async {
|
testWidgets('Name text field exists', (tester) async {
|
||||||
@@ -118,6 +125,7 @@ void main() {
|
|||||||
NewCharacterScreen(onCharacterCreated: (_, {bool testMode = false}) {}),
|
NewCharacterScreen(onCharacterCreated: (_, {bool testMode = false}) {}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// TextField 확인 (이름 입력 필드)
|
// TextField 확인 (이름 입력 필드)
|
||||||
expect(find.byType(TextField), findsOneWidget);
|
expect(find.byType(TextField), findsOneWidget);
|
||||||
|
|||||||
Reference in New Issue
Block a user