更新项目名称及相关引用,包括README、iOS/macOS/Linux配置、文档和代码中的包引用。同时更新版本号至1.3.5并清理无用的HarmonyOS配置文件。 - 修改所有代码中的包引用路径 - 更新各平台配置文件和安装脚本 - 清理HarmonyOS相关无用文件 - 更新应用版本号至1.3.5 - 修正文档中的项目名称引用
164 lines
6.3 KiB
Dart
164 lines
6.3 KiB
Dart
// 2026-04-22 | test_export_import.dart | 数据导出/导入逻辑验证 | 验证JSON格式正确性和导入识别
|
||
// 运行: dart run scripts/test_export_import.dart
|
||
|
||
import 'dart:convert';
|
||
import 'dart:io';
|
||
|
||
// ─── 模拟核心逻辑 ───
|
||
|
||
enum DataSource {
|
||
favorites('❤️ 收藏', 'favorites'),
|
||
shoppingList('🛒 购物清单', 'shopping_list'),
|
||
mealRecords('🍽️ 饮食记录', 'meal_records'),
|
||
cookingNotes('📝 烹饪笔记', 'cooking_notes'),
|
||
weeklyMenu('📅 每周菜单', 'weekly_menu'),
|
||
browseHistory('👀 浏览记录', 'browse_history');
|
||
|
||
final String label;
|
||
final String fileName;
|
||
const DataSource(this.label, this.fileName);
|
||
}
|
||
|
||
String _exportSingleSourceJson(DataSource source) {
|
||
switch (source) {
|
||
case DataSource.favorites:
|
||
return const JsonEncoder.withIndent(' ').convert([
|
||
{'id': 1, 'title': '红烧肉', 'favoriteType': 'recipe', 'favoriteAt': '2026-04-22'},
|
||
]);
|
||
case DataSource.shoppingList:
|
||
return const JsonEncoder.withIndent(' ').convert([
|
||
{'name': '鸡蛋', 'amount': '3', 'unit': '个', 'isChecked': false},
|
||
]);
|
||
case DataSource.mealRecords:
|
||
return const JsonEncoder.withIndent(' ').convert([
|
||
{'date': '2026-04-22', 'mealType': 'lunch', 'recipeId': 1},
|
||
]);
|
||
case DataSource.cookingNotes:
|
||
return const JsonEncoder.withIndent(' ').convert([
|
||
{'recipeId': 1, 'content': '少放盐', 'tags': ['tips']},
|
||
]);
|
||
case DataSource.weeklyMenu:
|
||
return const JsonEncoder.withIndent(' ').convert({
|
||
'weekStart': '2026-04-21',
|
||
'dailyMenus': {'Mon': {'breakfast': 1}},
|
||
});
|
||
case DataSource.browseHistory:
|
||
return const JsonEncoder.withIndent(' ').convert([
|
||
{'recipeId': 1, 'title': '红烧肉', 'viewCount': 3, 'viewedAt': '2026-04-22'},
|
||
]);
|
||
}
|
||
}
|
||
|
||
/// 新版 exportAll:先构建Map再编码
|
||
String exportAllNew() {
|
||
final Map<String, dynamic> allData = {};
|
||
for (final source in DataSource.values) {
|
||
final content = _exportSingleSourceJson(source);
|
||
try {
|
||
allData[source.fileName] = jsonDecode(content);
|
||
} catch (e) {
|
||
print(' ⚠️ 解码 ${source.fileName} 失败: $e');
|
||
}
|
||
}
|
||
return const JsonEncoder.withIndent(' ').convert(allData);
|
||
}
|
||
|
||
/// previewImport:标准格式识别 + List格式自动推断
|
||
({Map<DataSource, int> sourceCounts, String? error}) previewImport(String jsonContent) {
|
||
final result = <DataSource, int>{};
|
||
try {
|
||
final decoded = jsonDecode(jsonContent);
|
||
if (decoded is Map<String, dynamic>) {
|
||
for (final source in DataSource.values) {
|
||
if (decoded.containsKey(source.fileName)) {
|
||
final data = decoded[source.fileName];
|
||
if (data is List) {
|
||
result[source] = data.length;
|
||
} else if (data is Map && source == DataSource.weeklyMenu) {
|
||
result[source] = data.length;
|
||
}
|
||
}
|
||
}
|
||
} else if (decoded is List) {
|
||
if (decoded.isNotEmpty && decoded.first is Map<String, dynamic>) {
|
||
final inferred = _inferDataSource(decoded.first as Map<String, dynamic>);
|
||
result[inferred ?? DataSource.favorites] = decoded.length;
|
||
} else {
|
||
result[DataSource.favorites] = decoded.length;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
return (sourceCounts: result, error: e.toString());
|
||
}
|
||
return (sourceCounts: result, error: null);
|
||
}
|
||
|
||
DataSource? _inferDataSource(Map<String, dynamic> item) {
|
||
if (item.containsKey('favoriteType') || item.containsKey('favoriteAt')) return DataSource.favorites;
|
||
if (item.containsKey('isChecked') && item.containsKey('amount')) return DataSource.shoppingList;
|
||
if (item.containsKey('mealType') && item.containsKey('date')) return DataSource.mealRecords;
|
||
if (item.containsKey('recipeId') && item.containsKey('content')) return DataSource.cookingNotes;
|
||
if (item.containsKey('dailyMenus') || item.containsKey('weekStart')) return DataSource.weeklyMenu;
|
||
if (item.containsKey('viewCount') && item.containsKey('viewedAt')) return DataSource.browseHistory;
|
||
return null;
|
||
}
|
||
|
||
// ─── 测试 ───
|
||
|
||
void main() {
|
||
print('╔══════════════════════════════════════════════════╗');
|
||
print('║ 📦 数据导出/导入 逻辑验证 ║');
|
||
print('╚══════════════════════════════════════════════════╝\n');
|
||
|
||
testFullExport();
|
||
testSingleSourceImport();
|
||
|
||
print('\n╔══════════════════════════════════════════════════╗');
|
||
print('║ ✅ 全部测试完成 ║');
|
||
print('╚══════════════════════════════════════════════════╝');
|
||
}
|
||
|
||
void testFullExport() {
|
||
print('━━━ 1. 全量导出JSON格式验证 ━━━');
|
||
final json = exportAllNew();
|
||
try {
|
||
final decoded = jsonDecode(json) as Map<String, dynamic>;
|
||
print(' ✅ JSON可解析,${decoded.length} 个数据源');
|
||
for (final key in decoded.keys) {
|
||
final val = decoded[key];
|
||
final count = val is List ? val.length : (val is Map ? val.length : 0);
|
||
print(' - "$key": ${val.runtimeType} ($count项)');
|
||
}
|
||
} catch (e) {
|
||
print(' ❌ JSON解析失败: $e');
|
||
return;
|
||
}
|
||
|
||
final preview = previewImport(json);
|
||
if (preview.error != null) {
|
||
print(' ❌ previewImport失败: ${preview.error}');
|
||
} else {
|
||
print(' ✅ 识别 ${preview.sourceCounts.length}/${DataSource.values.length} 个数据源');
|
||
for (final entry in preview.sourceCounts.entries) {
|
||
print(' - ${entry.key.label}: ${entry.value} 条');
|
||
}
|
||
}
|
||
print('');
|
||
}
|
||
|
||
void testSingleSourceImport() {
|
||
print('━━━ 2. 单数据源导入自动推断 ━━━');
|
||
for (final source in DataSource.values) {
|
||
final json = _exportSingleSourceJson(source);
|
||
final preview = previewImport(json);
|
||
if (preview.sourceCounts.isNotEmpty) {
|
||
final e = preview.sourceCounts.entries.first;
|
||
final ok = e.key == source ? '✅' : '⚠️ 偏移';
|
||
print(' $ok ${source.label} → 推断为 ${e.key.label} (${e.value}条)');
|
||
} else {
|
||
print(' ❌ ${source.label} 无法识别');
|
||
}
|
||
}
|
||
print('');
|
||
}
|