feat(data): add PokemonDto with single-source JSON parsing + tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2f051c8da6
commit
f2dcba0fe2
93
lib/data/dto/pokemon_dto.dart
Normal file
93
lib/data/dto/pokemon_dto.dart
Normal file
@ -0,0 +1,93 @@
|
||||
import '../../domain/entities/pokemon.dart';
|
||||
|
||||
/// Conversion JSON <-> entité Pokemon. Unique endroit de parsing.
|
||||
class PokemonDto {
|
||||
PokemonDto._();
|
||||
|
||||
/// Mappe un nom de type français (API Tyradex) vers l'enum.
|
||||
static PokemonType frenchTypeToEnum(String frenchType) {
|
||||
const map = {
|
||||
'Normal': PokemonType.normal,
|
||||
'Combat': PokemonType.fighting,
|
||||
'Vol': PokemonType.flying,
|
||||
'Poison': PokemonType.poison,
|
||||
'Sol': PokemonType.ground,
|
||||
'Roche': PokemonType.rock,
|
||||
'Insecte': PokemonType.bug,
|
||||
'Spectre': PokemonType.ghost,
|
||||
'Acier': PokemonType.steel,
|
||||
'Feu': PokemonType.fire,
|
||||
'Eau': PokemonType.water,
|
||||
'Plante': PokemonType.grass,
|
||||
'Électrik': PokemonType.electric,
|
||||
'Psy': PokemonType.psychic,
|
||||
'Glace': PokemonType.ice,
|
||||
'Dragon': PokemonType.dragon,
|
||||
'Ténèbres': PokemonType.dark,
|
||||
'Fée': PokemonType.fairy,
|
||||
};
|
||||
return map[frenchType] ?? PokemonType.unknown;
|
||||
}
|
||||
|
||||
/// Construit une entité depuis la réponse Tyradex (objet unique ou élément de liste).
|
||||
/// [fallbackId] sert quand le JSON ne contient pas `pokedex_id`.
|
||||
static Pokemon fromTyradexJson(Map<String, dynamic> json, {int? fallbackId}) {
|
||||
final id = (json['pokedex_id'] as int?) ?? fallbackId!;
|
||||
final nameMap = json['name'] as Map<String, dynamic>?;
|
||||
final name = nameMap?['fr'] ?? nameMap?['en'] ?? 'unknown';
|
||||
|
||||
final List types = json['types'] ?? [];
|
||||
final type1 =
|
||||
types.isNotEmpty ? frenchTypeToEnum(types[0]['name']) : PokemonType.unknown;
|
||||
final type2 = types.length > 1 ? frenchTypeToEnum(types[1]['name']) : null;
|
||||
|
||||
final Map<String, dynamic>? stats = json['stats'];
|
||||
return Pokemon(
|
||||
name: name,
|
||||
id: id,
|
||||
type1: type1,
|
||||
type2: type2,
|
||||
hp: stats?['hp'] ?? 0,
|
||||
atk: stats?['atk'] ?? 0,
|
||||
def: stats?['def'] ?? 0,
|
||||
spd: stats?['vit'] ?? 0, // 'vit' = vitesse chez Tyradex
|
||||
description: json['category'],
|
||||
);
|
||||
}
|
||||
|
||||
/// Sérialise pour SQLite.
|
||||
static Map<String, dynamic> toDb(Pokemon p) {
|
||||
return {
|
||||
'name': p.name,
|
||||
'id': p.id,
|
||||
'type1': p.type1.name,
|
||||
'type2': p.type2?.name,
|
||||
'hp': p.hp,
|
||||
'atk': p.atk,
|
||||
'def': p.def,
|
||||
'spd': p.spd,
|
||||
'description': p.description,
|
||||
'isCaught': p.isCaught ? 1 : 0,
|
||||
'isSeen': p.isSeen ? 1 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Reconstruit depuis une ligne SQLite.
|
||||
static Pokemon fromDb(Map<String, dynamic> row) {
|
||||
return Pokemon(
|
||||
name: row['name'],
|
||||
id: row['id'],
|
||||
type1: PokemonType.values.firstWhere((e) => e.name == row['type1']),
|
||||
type2: row['type2'] != null
|
||||
? PokemonType.values.firstWhere((e) => e.name == row['type2'])
|
||||
: null,
|
||||
hp: row['hp'] ?? 0,
|
||||
atk: row['atk'] ?? 0,
|
||||
def: row['def'] ?? 0,
|
||||
spd: row['spd'] ?? 0,
|
||||
description: row['description'],
|
||||
isCaught: row['isCaught'] == 1 || row['isCaught'] == true,
|
||||
isSeen: row['isSeen'] == 1 || row['isSeen'] == true,
|
||||
);
|
||||
}
|
||||
}
|
||||
83
test/data/pokemon_dto_test.dart
Normal file
83
test/data/pokemon_dto_test.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pokeguess/domain/entities/pokemon.dart';
|
||||
import 'package:pokeguess/data/dto/pokemon_dto.dart';
|
||||
|
||||
void main() {
|
||||
group('PokemonDto.fromTyradexJson', () {
|
||||
test('parse un Pokémon complet avec deux types', () {
|
||||
final json = {
|
||||
'pokedex_id': 6,
|
||||
'name': {'fr': 'Dracaufeu', 'en': 'Charizard'},
|
||||
'types': [
|
||||
{'name': 'Feu'},
|
||||
{'name': 'Vol'},
|
||||
],
|
||||
'stats': {'hp': 78, 'atk': 84, 'def': 78, 'vit': 100},
|
||||
'category': 'Pokémon Flamme',
|
||||
};
|
||||
final p = PokemonDto.fromTyradexJson(json);
|
||||
expect(p.id, 6);
|
||||
expect(p.name, 'Dracaufeu');
|
||||
expect(p.type1, PokemonType.fire);
|
||||
expect(p.type2, PokemonType.flying);
|
||||
expect(p.hp, 78);
|
||||
expect(p.spd, 100);
|
||||
expect(p.description, 'Pokémon Flamme');
|
||||
});
|
||||
|
||||
test('utilise fallbackId quand pokedex_id absent', () {
|
||||
final json = {
|
||||
'name': {'fr': 'Bulbizarre'},
|
||||
'types': [{'name': 'Plante'}],
|
||||
'stats': {'hp': 45, 'atk': 49, 'def': 49, 'vit': 45},
|
||||
'category': 'Pokémon Graine',
|
||||
};
|
||||
final p = PokemonDto.fromTyradexJson(json, fallbackId: 1);
|
||||
expect(p.id, 1);
|
||||
expect(p.type2, isNull);
|
||||
expect(p.type1, PokemonType.grass);
|
||||
});
|
||||
|
||||
test('type inconnu mappé sur PokemonType.unknown', () {
|
||||
final json = {
|
||||
'pokedex_id': 999,
|
||||
'name': {'fr': 'Test'},
|
||||
'types': [{'name': 'TypeInexistant'}],
|
||||
'stats': {'hp': 1, 'atk': 1, 'def': 1, 'vit': 1},
|
||||
};
|
||||
final p = PokemonDto.fromTyradexJson(json);
|
||||
expect(p.type1, PokemonType.unknown);
|
||||
expect(p.description, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('PokemonDto round-trip DB', () {
|
||||
test('toDb puis fromDb reconstruit le Pokémon', () {
|
||||
const original = Pokemon(
|
||||
name: 'pikachu',
|
||||
id: 25,
|
||||
type1: PokemonType.electric,
|
||||
type2: null,
|
||||
hp: 35,
|
||||
atk: 55,
|
||||
def: 40,
|
||||
spd: 90,
|
||||
description: 'Souris',
|
||||
isCaught: true,
|
||||
isSeen: true,
|
||||
);
|
||||
final row = PokemonDto.toDb(original);
|
||||
expect(row['type1'], 'electric');
|
||||
expect(row['type2'], isNull);
|
||||
expect(row['isCaught'], 1);
|
||||
|
||||
final restored = PokemonDto.fromDb(row);
|
||||
expect(restored.name, 'pikachu');
|
||||
expect(restored.id, 25);
|
||||
expect(restored.type1, PokemonType.electric);
|
||||
expect(restored.type2, isNull);
|
||||
expect(restored.isCaught, true);
|
||||
expect(restored.isSeen, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user