2026-02-15 15:13:12 +00:00
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
|
|
import 'package:ponshu_room_lite/models/sake_item.dart';
|
|
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
|
group('SakeItem - Construction', () {
|
|
|
|
|
|
test('should create SakeItem with required fields', () {
|
|
|
|
|
|
final sake = SakeItem(
|
|
|
|
|
|
id: 'test-001',
|
|
|
|
|
|
displayData: DisplayData(
|
|
|
|
|
|
name: '獺祭 純米大吟醸',
|
|
|
|
|
|
brewery: '旭酒造',
|
|
|
|
|
|
prefecture: '山口県',
|
|
|
|
|
|
imagePaths: ['/path/to/image.jpg'],
|
|
|
|
|
|
),
|
|
|
|
|
|
hiddenSpecs: HiddenSpecs(
|
|
|
|
|
|
description: 'フルーティーで華やかな香りが特徴',
|
|
|
|
|
|
tasteStats: {'aroma': 5, 'sweetness': 3, 'acidity': 2, 'bitterness': 2, 'body': 3},
|
|
|
|
|
|
flavorTags: ['フルーティー', '華やか'],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
expect(sake.id, 'test-001');
|
|
|
|
|
|
expect(sake.displayData.name, '獺祭 純米大吟醸');
|
|
|
|
|
|
expect(sake.displayData.brewery, '旭酒造');
|
|
|
|
|
|
expect(sake.displayData.prefecture, '山口県');
|
|
|
|
|
|
expect(sake.displayData.imagePaths.length, 1);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('should create SakeItem with default values for optional fields', () {
|
|
|
|
|
|
final sake = SakeItem(
|
|
|
|
|
|
id: 'test-002',
|
|
|
|
|
|
displayData: DisplayData(
|
|
|
|
|
|
name: 'テスト銘柄',
|
|
|
|
|
|
brewery: 'テスト蔵元',
|
|
|
|
|
|
prefecture: '東京都',
|
|
|
|
|
|
imagePaths: [],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
expect(sake.hiddenSpecs.tasteStats, isEmpty);
|
|
|
|
|
|
expect(sake.hiddenSpecs.flavorTags, isEmpty);
|
|
|
|
|
|
expect(sake.userData.isFavorite, false);
|
|
|
|
|
|
expect(sake.userData.isUserEdited, false);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
group('SakeItem - DisplayData getters', () {
|
|
|
|
|
|
test('should use primary name when sakenowa data is not set', () {
|
|
|
|
|
|
final sake = SakeItem(
|
|
|
|
|
|
id: 'test-003',
|
|
|
|
|
|
displayData: DisplayData(
|
|
|
|
|
|
name: 'AI解析名',
|
|
|
|
|
|
brewery: 'AI蔵元',
|
|
|
|
|
|
prefecture: 'AI県',
|
|
|
|
|
|
imagePaths: [],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
expect(sake.displayData.displayName, 'AI解析名');
|
|
|
|
|
|
expect(sake.displayData.displayBrewery, 'AI蔵元');
|
|
|
|
|
|
expect(sake.displayData.displayPrefecture, 'AI県');
|
|
|
|
|
|
expect(sake.displayData.hasSakenowaData, false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('should prioritize sakenowa data over primary data', () {
|
|
|
|
|
|
final sake = SakeItem(
|
|
|
|
|
|
id: 'test-004',
|
|
|
|
|
|
displayData: DisplayData(
|
|
|
|
|
|
name: 'AI解析名',
|
|
|
|
|
|
brewery: 'AI蔵元',
|
|
|
|
|
|
prefecture: 'AI県',
|
|
|
|
|
|
imagePaths: [],
|
|
|
|
|
|
sakenowaName: 'さけのわ統一名',
|
|
|
|
|
|
sakenowaBrewery: 'さけのわ蔵元',
|
|
|
|
|
|
sakenowaPrefecture: 'さけのわ県',
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
expect(sake.displayData.displayName, 'さけのわ統一名');
|
|
|
|
|
|
expect(sake.displayData.displayBrewery, 'さけのわ蔵元');
|
|
|
|
|
|
expect(sake.displayData.displayPrefecture, 'さけのわ県');
|
|
|
|
|
|
expect(sake.displayData.hasSakenowaData, true);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
group('SakeItem - copyWith', () {
|
|
|
|
|
|
late SakeItem baseSake;
|
|
|
|
|
|
|
|
|
|
|
|
setUp(() {
|
|
|
|
|
|
baseSake = SakeItem(
|
|
|
|
|
|
id: 'test-005',
|
|
|
|
|
|
displayData: DisplayData(
|
|
|
|
|
|
name: '元の銘柄',
|
|
|
|
|
|
brewery: '元の蔵元',
|
|
|
|
|
|
prefecture: '元の県',
|
|
|
|
|
|
imagePaths: ['/path/original.jpg'],
|
|
|
|
|
|
catchCopy: '元のキャッチコピー',
|
|
|
|
|
|
),
|
|
|
|
|
|
hiddenSpecs: HiddenSpecs(
|
|
|
|
|
|
description: '元の説明',
|
|
|
|
|
|
tasteStats: {'aroma': 3, 'sweetness': 3, 'acidity': 3, 'bitterness': 3, 'body': 3},
|
|
|
|
|
|
flavorTags: ['元のタグ'],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('should update name while keeping other fields', () {
|
|
|
|
|
|
final updated = baseSake.copyWith(name: '新しい銘柄');
|
|
|
|
|
|
|
|
|
|
|
|
expect(updated.displayData.name, '新しい銘柄');
|
|
|
|
|
|
expect(updated.displayData.brewery, '元の蔵元');
|
|
|
|
|
|
expect(updated.displayData.prefecture, '元の県');
|
|
|
|
|
|
expect(updated.id, 'test-005');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('should update tasteStats while keeping other fields', () {
|
|
|
|
|
|
final newStats = {'aroma': 5, 'sweetness': 1, 'acidity': 4, 'sharpness': 2, 'body': 5};
|
|
|
|
|
|
final updated = baseSake.copyWith(tasteStats: newStats);
|
|
|
|
|
|
|
|
|
|
|
|
expect(updated.hiddenSpecs.tasteStats, newStats);
|
|
|
|
|
|
expect(updated.displayData.name, '元の銘柄');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('should update favorite status', () {
|
|
|
|
|
|
final updated = baseSake.copyWith(isFavorite: true);
|
|
|
|
|
|
|
|
|
|
|
|
expect(updated.userData.isFavorite, true);
|
|
|
|
|
|
expect(updated.displayData.name, '元の銘柄');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('should update imagePaths', () {
|
|
|
|
|
|
final newPaths = ['/path/new1.jpg', '/path/new2.jpg'];
|
|
|
|
|
|
final updated = baseSake.copyWith(imagePaths: newPaths);
|
|
|
|
|
|
|
|
|
|
|
|
expect(updated.displayData.imagePaths, newPaths);
|
|
|
|
|
|
expect(updated.displayData.imagePaths.length, 2);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-16 23:40:48 +00:00
|
|
|
|
group('SakeItem - ensureMigrated', () {
|
|
|
|
|
|
test('レガシーフィールドからdisplayDataを構築する', () {
|
|
|
|
|
|
final sake = SakeItem(
|
|
|
|
|
|
id: 'legacy-001',
|
|
|
|
|
|
legacyName: '出羽桜',
|
|
|
|
|
|
legacyBrand: '出羽桜酒造',
|
|
|
|
|
|
legacyPrefecture: '山形県',
|
|
|
|
|
|
legacyCatchCopy: '花と夢と',
|
|
|
|
|
|
legacyImagePaths: ['/path/legacy.jpg'],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// displayData未設定の状態でgetterがlegacyから返す
|
|
|
|
|
|
expect(sake.displayData.name, '出羽桜');
|
|
|
|
|
|
expect(sake.displayData.brewery, '出羽桜酒造');
|
|
|
|
|
|
expect(sake.displayData.prefecture, '山形県');
|
|
|
|
|
|
|
|
|
|
|
|
// ensureMigratedで新構造に昇格
|
|
|
|
|
|
final migrated = sake.ensureMigrated();
|
|
|
|
|
|
expect(migrated, true); // 移行が実行された
|
|
|
|
|
|
|
|
|
|
|
|
// 2回目はfalseを返す(既に移行済み)
|
|
|
|
|
|
final again = sake.ensureMigrated();
|
|
|
|
|
|
expect(again, false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('displayDataが既に設定されている場合はfalseを返す', () {
|
|
|
|
|
|
final sake = SakeItem(
|
|
|
|
|
|
id: 'new-001',
|
|
|
|
|
|
displayData: DisplayData(
|
|
|
|
|
|
name: '新政',
|
|
|
|
|
|
brewery: '新政酒造',
|
|
|
|
|
|
prefecture: '秋田県',
|
|
|
|
|
|
imagePaths: [],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
final migrated = sake.ensureMigrated();
|
|
|
|
|
|
expect(migrated, false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('legacyNameがnullの場合はUnknownにフォールバックする', () {
|
|
|
|
|
|
final sake = SakeItem(id: 'legacy-002');
|
|
|
|
|
|
|
|
|
|
|
|
sake.ensureMigrated();
|
|
|
|
|
|
|
|
|
|
|
|
expect(sake.displayData.name, 'Unknown');
|
|
|
|
|
|
expect(sake.displayData.brewery, 'Unknown');
|
|
|
|
|
|
expect(sake.displayData.prefecture, 'Unknown');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-15 15:13:12 +00:00
|
|
|
|
group('SakeItem - HiddenSpecs / TasteStats', () {
|
|
|
|
|
|
test('should return SakeTasteStats from tasteStats map', () {
|
|
|
|
|
|
final sake = SakeItem(
|
|
|
|
|
|
id: 'test-006',
|
|
|
|
|
|
displayData: DisplayData(
|
|
|
|
|
|
name: 'テスト',
|
|
|
|
|
|
brewery: 'テスト蔵',
|
|
|
|
|
|
prefecture: 'テスト県',
|
|
|
|
|
|
imagePaths: [],
|
|
|
|
|
|
),
|
|
|
|
|
|
hiddenSpecs: HiddenSpecs(
|
|
|
|
|
|
description: '',
|
|
|
|
|
|
tasteStats: {'aroma': 5, 'sweetness': 3, 'acidity': 2, 'bitterness': 4, 'body': 1},
|
|
|
|
|
|
flavorTags: [],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
final stats = sake.hiddenSpecs.sakeTasteStats;
|
|
|
|
|
|
expect(stats.aroma, 5);
|
|
|
|
|
|
expect(stats.sweetness, 3);
|
|
|
|
|
|
expect(stats.acidity, 2);
|
|
|
|
|
|
expect(stats.bitterness, 4);
|
|
|
|
|
|
expect(stats.body, 1);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('should return zero stats for empty tasteStats', () {
|
|
|
|
|
|
final sake = SakeItem(
|
|
|
|
|
|
id: 'test-007',
|
|
|
|
|
|
displayData: DisplayData(
|
|
|
|
|
|
name: 'テスト',
|
|
|
|
|
|
brewery: 'テスト蔵',
|
|
|
|
|
|
prefecture: 'テスト県',
|
|
|
|
|
|
imagePaths: [],
|
|
|
|
|
|
),
|
|
|
|
|
|
hiddenSpecs: HiddenSpecs(
|
|
|
|
|
|
description: '',
|
|
|
|
|
|
tasteStats: {},
|
|
|
|
|
|
flavorTags: [],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
final stats = sake.hiddenSpecs.sakeTasteStats;
|
|
|
|
|
|
expect(stats.aroma, 0);
|
|
|
|
|
|
expect(stats.sweetness, 0);
|
|
|
|
|
|
expect(stats.acidity, 0);
|
|
|
|
|
|
expect(stats.bitterness, 0);
|
|
|
|
|
|
expect(stats.body, 0);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|