Files
observer/flutter_app/test/model_catalog_section_test.dart
T
2026-08-31 18:35:58 +08:00

151 lines
4.7 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart' show sha256;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:observer/camera/model_catalog_section.dart';
import 'package:observer/models/model_manager.dart';
const _modelBytes = [1, 2, 3, 4, 5, 6, 7, 8];
String _shaHex(List<int> bytes) => sha256.convert(bytes).toString();
/// 分两段到达的下载流(中间 50ms 停顿,供取消测试在下载中触发)
Stream<List<int>> _delayedChunks() async* {
yield [1, 2, 3, 4];
await Future<void>.delayed(const Duration(milliseconds: 50));
yield [5, 6, 7, 8];
}
Map<String, dynamic> _item() => {
'datasetId': 7,
'datasetName': '环颈雉鸡数据集',
'version': 'v1.0.0',
'labels': ['pheasant', 'suspect'],
'sizeBytes': _modelBytes.length,
'sha256': _shaHex(_modelBytes),
'downloadUrl': '/download/models/7/latest.tflite',
'coverUrl': '/api/v1/app/cover?namePrefix=RNPHE',
};
void main() {
late Directory root;
setUp(() async {
root = await Directory.systemTemp.createTemp('catalog-section-test');
});
tearDown(() => root.delete(recursive: true));
ModelManager manager() => ModelManager(
baseUrl: 'http://test.local',
client: MockClient((req) async {
if (req.url.path == '/api/v1/app/update') {
return http.Response.bytes(
utf8.encode(jsonEncode({
'code': 0,
'message': 'ok',
'data': {'models': [_item()]}
})),
200);
}
if (req.url.path.startsWith('/download/models/')) {
return http.Response.bytes(_modelBytes, 200);
}
return http.Response('not found', 404);
}),
rootDir: () async => root,
);
Future<void> pumpSection(WidgetTester tester, ModelManager m) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: ModelCatalogSection(manager: m))));
await tester.pumpAndSettle();
}
testWidgets('未下载:显示使用按钮,点击后下载完成自动变为已使用', (tester) async {
final m = manager();
await m.refresh();
await pumpSection(tester, m);
expect(find.text('环颈雉鸡数据集'), findsOneWidget);
expect(find.text('使用'), findsOneWidget);
await tester.tap(find.text('使用'));
await tester.pumpAndSettle();
expect(m.isDownloaded(7), isTrue);
expect(m.isActive(7), isTrue);
expect(find.text('已使用'), findsOneWidget);
});
testWidgets('已激活:再次点击取消使用', (tester) async {
final m = manager();
await m.refresh();
await m.downloadModel(m.catalog.first);
await pumpSection(tester, m);
expect(find.text('已使用'), findsOneWidget);
await tester.tap(find.text('已使用'));
await tester.pumpAndSettle();
expect(m.isActive(7), isFalse);
expect(find.text('使用'), findsOneWidget);
});
testWidgets('目录为空:显示暂无已发布模型', (tester) async {
final m = ModelManager(
baseUrl: 'http://test.local',
client: MockClient((_) async => http.Response.bytes(
utf8.encode(jsonEncode(
{'code': 0, 'message': 'ok', 'data': {'models': []}})),
200)),
rootDir: () async => root,
);
await m.refresh();
await pumpSection(tester, m);
expect(find.text('暂无已发布模型'), findsOneWidget);
});
testWidgets('下载中:显示进度和取消按钮,取消后恢复使用', (tester) async {
final m = ModelManager(
baseUrl: 'http://test.local',
client: MockClient((req) async {
if (req.url.path == '/api/v1/app/update') {
return http.Response.bytes(
utf8.encode(jsonEncode({
'code': 0,
'message': 'ok',
'data': {'models': [_item()]}
})),
200);
}
if (req.url.path.startsWith('/download/models/')) {
return http.Response.fromStream(http.StreamedResponse(
_delayedChunks(), 200,
contentLength: _modelBytes.length));
}
return http.Response('not found', 404);
}),
rootDir: () async => root,
);
await m.refresh();
await pumpSection(tester, m);
await tester.tap(find.text('使用'));
await tester.pump(); // 首个分块到达,进度条与取消按钮出现
expect(find.text('取消'), findsOneWidget);
await tester.tap(find.text('取消'));
await tester.pumpAndSettle();
expect(m.isDownloaded(7), isFalse);
expect(m.isActive(7), isFalse);
expect(find.text('使用'), findsOneWidget);
});
}