Skip to content
代码片段 群组 项目
提交 adbc024f 编辑于 作者: ling zhang's avatar ling zhang 提交者: Raymond Liao
浏览文件

feat: #224 系统本地存储子组列表数据

上级 f646ffbd
No related branches found
No related tags found
无相关合并请求
显示
556 个添加135 个删除
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/modules/todo_list/widgets/member_entity.dart';
import 'package:jihu_gitlab_app/modules/issues/issue_draft_entity.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project.dart';
import 'package:jihu_gitlab_app/modules/todo_list/widgets/member_entity.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DbManager {
static const int _version = 3;
static const int _version = 4;
static const String _name = "jihu.db";
static final DbManager _instance = DbManager._internal();
Database? _db;
......@@ -44,9 +45,56 @@ class DbManager {
case 3:
db.execute(IssueDraftEntity.createTableSql());
break;
case 4:
db.execute(GroupAndProjectEntity.createTableSql);
break;
default:
break;
}
}
}
Future<int> insert(String table, Map<String, Object?> values, {String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm}) async {
var db = await getDb();
return db.insert(table, values, nullColumnHack: nullColumnHack, conflictAlgorithm: conflictAlgorithm);
}
Future<List<int>> batchInsert(String tableName, List<Map<String, dynamic>> values, {String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm}) async {
var db = await getDb();
return db.transaction((txn) async {
List<int> result = [];
for (var element in values) {
int id = await txn.insert(tableName, element, nullColumnHack: nullColumnHack, conflictAlgorithm: conflictAlgorithm);
result.add(id);
}
return result;
});
}
Future<int> batchUpdate(String tableName, List<Map<String, dynamic>> values, {String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm}) async {
var db = await getDb();
return await db.transaction<int>((txn) async {
int result = 0;
for (var value in values) {
int count = await txn.update(tableName, value, where: " id = ? ", whereArgs: [value['id']]);
result += count;
}
return result;
});
}
Future<Map<String, dynamic>?> findOne(String tableName, {bool? distinct, List<String>? columns, String? where, List<Object?>? whereArgs}) async {
var db = await getDb();
var list = await db.query(tableName, distinct: distinct, columns: columns, where: where, whereArgs: whereArgs, limit: 1);
return list.isNotEmpty ? list[0] : null;
}
Future<List<Map<String, dynamic>>> find(String tableName, {bool? distinct, List<String>? columns, String? where, List<Object?>? whereArgs}) async {
var db = await getDb();
return await db.query(tableName, distinct: distinct, columns: columns, where: where, whereArgs: whereArgs);
}
Future<int> delete(String tableName, {String? where, List<Object?>? whereArgs}) async {
return (await getDb()).delete(tableName, where: where, whereArgs: whereArgs);
}
}
import 'package:get_it/get_it.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/subgroups/subgroup_list_model.dart';
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerFactory<SubgroupListModel>(() => SubgroupListModel());
}
class ListComparator {
static ListCompareResult<T> compare<T>(List<T> oldValue, List<T> newValue) {
List<T> shouldBeAdd = [];
List<Pair<T>> shouldBeUpdate = [];
for (var value in newValue) {
var where = oldValue.where((e) => e == value);
if (where.isEmpty) {
shouldBeAdd.add(value);
} else {
shouldBeUpdate.add(Pair(where.first, value));
}
}
return ListCompareResult(shouldBeAdd, shouldBeUpdate);
}
}
class ListCompareResult<T> {
List<T> add;
List<Pair<T>> update;
bool get hasAdd => add.isNotEmpty;
bool get hasUpdate => update.isNotEmpty;
ListCompareResult(this.add, this.update);
}
class Pair<T> {
T left;
T right;
Pair(this.left, this.right);
}
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/core/net/http_client.dart';
typedef HasNextPage = bool Function();
typedef DataProcessor<T> = Future<bool> Function(T data, int page, int pageSize);
class Synchronizer<T> {
int _page = 1;
late int _pageSize;
bool _hasNextPage = false;
final String url;
final DataProcessor<T> dataProcessor;
Synchronizer({required this.url, required this.dataProcessor, int pageSize = 50}) {
_pageSize = pageSize;
}
Future<bool> run() async {
bool result = true;
try {
do {
var response = await HttpClient.instance().get<T>(_contactPagingArguments());
_hasNextPage = await dataProcessor.call(response.body(), _page, _pageSize);
_page++;
} while (_hasNextPage);
} catch (e) {
result = false;
debugPrint(e.toString());
}
return Future(() => result);
}
String _contactPagingArguments() {
if (url.contains("?")) {
return '$url&page=$_page&per_page=$_pageSize';
}
return '$url?page=$_page&per_page=$_pageSize';
}
}
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/core/local_storage.dart';
import 'package:jihu_gitlab_app/core/net/http_client.dart';
import 'package:jihu_gitlab_app/core/token_provider.dart';
......@@ -83,6 +84,11 @@ class UserProvider {
await LocalStorage.remove(_persistencePersonalAccessTokenKey);
}
@visibleForTesting
void reset(User user) {
_user = user;
}
static User? get user => _user;
static bool get isSelfManagedGitLab => _isSelfManaged;
......@@ -90,6 +96,8 @@ class UserProvider {
static String? get privateHostUrl => _privateHostUrl;
static String? get personalAccessToken => _personalAccessToken;
static int? get userId => _user?.id;
}
class User {
......
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/core/widgets/avatar/avatar.dart';
class AvatarAndName extends StatefulWidget {
class AvatarAndName extends StatelessWidget {
final String username;
final String avatarUrl;
const AvatarAndName({required this.username, required this.avatarUrl, Key? key}) : super(key: key);
@override
State<AvatarAndName> createState() => _AvatarAndNameState();
}
class _AvatarAndNameState extends State<AvatarAndName> {
@override
Widget build(BuildContext context) {
return _buildWidget();
......@@ -19,17 +14,16 @@ class _AvatarAndNameState extends State<AvatarAndName> {
Widget _buildWidget() {
return Container(
decoration: const BoxDecoration(color: Colors.white),
margin: const EdgeInsets.only(left: 4),
height: 32,
child: Row(
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 12),
child: Avatar(avatarUrl: widget.avatarUrl, size: 32),
child: Avatar(avatarUrl: avatarUrl, size: 32),
),
Text(
widget.username,
username,
style: const TextStyle(fontWeight: FontWeight.w400, fontSize: 16, color: Color(0XFF03162F)),
)
],
......
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/core/db_manager.dart';
import 'package:jihu_gitlab_app/core/dependency_injector.dart';
import 'package:jihu_gitlab_app/core/global_keys.dart';
import 'package:jihu_gitlab_app/core/local_storage.dart';
import 'package:jihu_gitlab_app/core/plugins/install_apk_plugin.dart';
......@@ -21,6 +22,7 @@ void main() async {
await TokenProvider().restore();
await UserProvider().restore();
await DbManager.instance().open();
setupLocator();
runApp(
MultiProvider(
providers: [ChangeNotifierProvider(create: (context) => TokenProvider())],
......
import 'package:jihu_gitlab_app/modules/projects/group_details/subgroups/subgroup_list_model.dart';
class GroupAndProject {
int id;
String name;
SubgroupItemType? type;
String? relativePath;
int? parentId;
GroupAndProject(this.id, this.name, this.type, this.relativePath, this.parentId);
String get icon => type == SubgroupItemType.project ? 'assets/images/project_icon.png' : 'assets/images/group_icon.png';
}
class GroupAndProjectEntity {
static const tableName = "groups_and_projects";
int? id;
final int userId;
final int iid;
final String name;
final String? type;
final String? relativePath;
final int? parentId;
final int version;
GroupAndProjectEntity._internal(this.id, this.userId, this.iid, this.name, this.type, this.relativePath, this.parentId, this.version);
Map<String, dynamic> toMap() {
return {"id": id, "user_id": userId, "iid": iid, "name": name, "type": type, "relative_path": relativePath, "parent_id": parentId, "version": version};
}
static GroupAndProjectEntity createSubgroupEntity(Map<String, dynamic> json, int userId, int parentId, int version) {
return GroupAndProjectEntity._internal(null, userId, json['id'], json['name'], SubgroupItemType.subgroup.name, (json['full_path'] ?? '').substring(1), parentId, version);
}
static GroupAndProjectEntity createProjectEntity(Map<String, dynamic> json, int userId, int parentId, int version) {
return GroupAndProjectEntity._internal(null, userId, json['id'], json['name'], SubgroupItemType.project.name, (json['path_with_namespace'] ?? '').substring(1), parentId, version);
}
static GroupAndProjectEntity restore(Map<String, dynamic> map) {
return GroupAndProjectEntity._internal(map['id'], map['user_id'], map['iid'], map['name'], map['type'], map['relative_path'], map['parent_id'], map['version']);
}
GroupAndProject asDomain() {
return GroupAndProject(iid, name, SubgroupItemType.values.where((e) => e.name == type).first, relativePath, parentId);
}
@override
bool operator ==(Object other) =>
identical(this, other) || other is GroupAndProjectEntity && runtimeType == other.runtimeType && userId == other.userId && iid == other.iid && parentId == other.parentId;
@override
int get hashCode => userId.hashCode ^ iid.hashCode ^ parentId.hashCode;
static String get createTableSql => """
create table $tableName(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
iid INTEGER NOT NULL,
name TEXT ,
type TEXT ,
relative_path TEXT,
parent_id int,
version INTEGER
);
""";
}
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/core/list_comparator.dart';
import 'package:jihu_gitlab_app/core/synchronizer.dart';
import 'package:jihu_gitlab_app/core/user_provider.dart';
import 'package:jihu_gitlab_app/core/widgets/selector/data_provider.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project_repository.dart';
class GroupAndProjectProvider extends DataProvider<GroupAndProject> {
late int _userId;
late int _parentId;
late GroupAndProjectRepository _repository;
GroupAndProjectProvider({required int userId, required int parentId}) {
_userId = userId;
_parentId = parentId;
_repository = GroupAndProjectRepository();
}
@override
Future<List<GroupAndProject>> loadFromLocal() async {
List<GroupAndProjectEntity> groupAndProjectEntities = await _repository.query(userId: _userId, parentId: _parentId);
return groupAndProjectEntities.map((e) => e.asDomain()).toList();
}
@override
Future<bool> syncFromRemote() async {
int version = DateTime.now().millisecondsSinceEpoch;
List<bool> results = await Future.wait([_syncProjects(version), _syncSubgroups(version)]);
return Future(() => results.every((e) => e));
}
Future<bool> _syncSubgroups(int version) async {
return Synchronizer<dynamic>(
url: '/api/v4/groups/$_parentId/subgroups?all_available=true',
dataProcessor: (data, page, pageSize) async {
List<GroupAndProjectEntity> subgroupEntities = (data as List).map((e) => GroupAndProjectEntity.createSubgroupEntity(e, UserProvider.userId!, _parentId, version)).toList(growable: false);
await _save(subgroupEntities, version);
return subgroupEntities.length >= pageSize;
}).run();
}
Future<bool> _syncProjects(int version) async {
return Synchronizer<dynamic>(
url: '/api/v4/groups/$_parentId?all_available=true',
dataProcessor: (data, page, pageSize) async {
List<GroupAndProjectEntity> subgroupEntities =
((data['projects'] ?? []) as List).map((e) => GroupAndProjectEntity.createProjectEntity(e, UserProvider.userId!, _parentId, version)).toList(growable: false);
await _save(subgroupEntities, version);
return subgroupEntities.length >= pageSize;
}).run();
}
Future<void> _save(List<GroupAndProjectEntity> subgroupEntities, int version) async {
List<int> iidList = subgroupEntities.map((e) => e.iid).toList();
List<GroupAndProjectEntity> exists = await _repository.query(userId: _userId, parentId: _parentId, iidList: iidList);
ListCompareResult<GroupAndProjectEntity> result = ListComparator.compare<GroupAndProjectEntity>(exists, subgroupEntities);
if (result.hasAdd) {
await _repository.insert(result.add);
}
if (result.hasUpdate) {
var list = result.update.map((e) {
e.right.id = e.left.id!;
return e.right;
}).toList();
await _repository.update(list);
}
await _repository.deleteLessThan(_userId, _parentId, version);
}
@visibleForTesting
void injectRepositoryForTesting(GroupAndProjectRepository groupAndProjectRepository) {
_repository = groupAndProjectRepository;
}
}
import 'package:jihu_gitlab_app/core/db_manager.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project.dart';
class GroupAndProjectRepository {
insert(List<GroupAndProjectEntity> subgroupEntities) async {
return await DbManager.instance().batchInsert(GroupAndProjectEntity.tableName, subgroupEntities.map((e) => e.toMap()).toList());
}
update(List<GroupAndProjectEntity> subgroupEntities) async {
return await DbManager.instance().batchUpdate(GroupAndProjectEntity.tableName, subgroupEntities.map((e) => e.toMap()).toList());
}
Future<int> deleteLessThan(int userId, int parentId, int version) async {
return await DbManager.instance().delete(GroupAndProjectEntity.tableName, where: ' user_id = ? and parent_id = ? and version < ? ', whereArgs: [userId, parentId, version]);
}
Future<List<GroupAndProjectEntity>> query({required int userId, required int parentId, List<int>? iidList}) async {
String where = 'user_id = ? and parent_id = ? ';
List<Object> whereArgs = [userId, parentId];
if (iidList != null) {
where += 'and iid in (?)';
whereArgs.add(iidList.join(","));
}
var list = await DbManager.instance().find(GroupAndProjectEntity.tableName, where: where, whereArgs: whereArgs);
return list.map((e) => GroupAndProjectEntity.restore(e)).toList();
}
}
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/core/load_state.dart';
import 'package:jihu_gitlab_app/core/net/http_client.dart';
import 'package:jihu_gitlab_app/core/user_provider.dart';
import 'package:jihu_gitlab_app/core/widgets/selector/data_provider.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project_provider.dart';
enum SubgroupItemType { group, project }
enum SubgroupItemType { group, project, subgroup }
class SubgroupListModel {
List<SubgroupItem> subgroupItems = [];
......@@ -12,10 +16,15 @@ class SubgroupListModel {
LoadState _loadState = LoadState.noItemState;
late String _relativePath;
late int _groupId;
final List<GroupAndProject> _data = [];
late ValueNotifier<List<GroupAndProject>> _notifier;
DataProvider<GroupAndProject>? _dataProvider;
init({required String relativePath, required int groupId}) {
_relativePath = relativePath;
_groupId = groupId;
_dataProvider ??= GroupAndProjectProvider(userId: UserProvider.userId!, parentId: _groupId);
_notifier = ValueNotifier(_data);
}
Future<List<SubgroupItem>> fetchSubGroups(String relativePath) async {
......@@ -58,6 +67,23 @@ class SubgroupListModel {
}
}
Future<void> loadData() async {
_loadLocalDataAndNotify();
_dataProvider?.syncFromRemote().then((value) {
if (value) {
_loadLocalDataAndNotify();
}
});
}
Future<void> _loadLocalDataAndNotify() async {
List<GroupAndProject> data = await _dataProvider!.loadFromLocal();
_data.clear();
_data.addAll(data);
_notifier.value = data;
_loadState = LoadState.successState;
}
Future<bool> loadMore() async {
return Future.value(true);
}
......@@ -69,6 +95,11 @@ class SubgroupListModel {
bool get hasNextPage {
return _hasNextPage;
}
ValueNotifier<List<GroupAndProject>> get notifier => _notifier;
@visibleForTesting
void injectDataProviderForTesting(DataProvider<GroupAndProject> dataProvider) => _dataProvider = dataProvider;
}
@immutable
......
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:jihu_gitlab_app/core/dependency_injector.dart';
import 'package:jihu_gitlab_app/core/load_state.dart';
import 'package:jihu_gitlab_app/core/widgets/no_data_view.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/subgroups/project/project_issues_page.dart';
import '../subgroup_page.dart';
......@@ -18,20 +20,18 @@ class SubgroupListPage extends StatefulWidget {
}
class _SubgroupListPageState extends State<SubgroupListPage> {
final SubgroupListModel _model = SubgroupListModel();
final SubgroupListModel _model = locator<SubgroupListModel>();
EasyRefreshController? _refreshController;
@override
void initState() {
super.initState();
_model.init(relativePath: widget.relativePath, groupId: widget.groupId);
if (_refreshController == null) {
_refreshController = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
_onRefresh();
}
_model.loadData();
_refreshController ??= EasyRefreshController(
controlFinishRefresh: false,
controlFinishLoad: false,
);
}
@override
......@@ -40,99 +40,79 @@ class _SubgroupListPageState extends State<SubgroupListPage> {
super.dispose();
}
void _onRefresh() async {
bool success = await _model.refresh();
setState(() {});
if (success) {
_refreshController?.finishRefresh();
_refreshController?.resetFooter();
} else {
_refreshController?.finishRefresh(IndicatorResult.fail);
}
}
void _loadMore() async {
bool success = await _model.loadMore();
setState(() {});
if (success) {
_refreshController?.finishLoad(_model.hasNextPage ? IndicatorResult.success : IndicatorResult.noMore);
} else {
_refreshController?.finishLoad(IndicatorResult.fail);
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: EasyRefresh(
controller: _refreshController!,
header: const ClassicHeader(),
footer: const ClassicFooter(),
onRefresh: _onRefresh,
onLoad: _loadMore,
// header: const ClassicHeader(),
// footer: const ClassicFooter(),
// onLoad: _loadMore,
child: subgroupListView(),
));
}
Widget subgroupListView() {
var model = _model;
if (_model.loadState == LoadState.successState && _model.subgroupItems.isEmpty) {
return const NoDataView(icon: 'assets/images/no_groups.png');
} else {
final textTheme = Theme.of(context).textTheme;
return ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: ListView.separated(
itemCount: model.subgroupItems.length,
separatorBuilder: (context, index) {
return const Divider(height: .5, indent: 12.0, endIndent: 12.0, color: Color(0xFFDDDDDD));
},
itemBuilder: (context, index) => InkWell(
onTap: () {
final item = model.subgroupItems[index];
if (item.type == SubgroupItemType.group) {
final params = <String, dynamic>{'groupId': item.id, 'name': item.name, 'relativePath': item.relativePath};
Navigator.of(context).pushNamed(SubgroupPage.routeName, arguments: params);
} else {
final params = <String, dynamic>{'projectId': item.id, 'name': item.name, 'relativePath': item.relativePath, "groupId": item.groupId};
Navigator.of(context).pushNamed(ProjectIssuesPage.routeName, arguments: params);
}
},
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
),
height: 80,
padding: const EdgeInsets.only(left: 12, right: 12),
child: SizedBox(
height: 79,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
model.subgroupItems[index].icon!,
width: 16,
height: 16,
),
const SizedBox(width: 12),
Text(
model.subgroupItems[index].name,
style: textTheme.titleLarge,
),
const Spacer(
flex: 1,
final textTheme = Theme.of(context).textTheme;
return ValueListenableBuilder<List<GroupAndProject>>(
valueListenable: _model.notifier,
builder: (BuildContext context, List<GroupAndProject> data, Widget? child) {
if (_model.loadState == LoadState.successState && data.isEmpty) {
return const NoDataView(icon: 'assets/images/no_groups.png');
}
return ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: ListView.separated(
itemCount: data.length,
separatorBuilder: (context, index) {
return const Divider(height: .5, indent: 12.0, endIndent: 12.0, color: Color(0xFFDDDDDD));
},
itemBuilder: (context, index) => InkWell(
onTap: () {
final item = data[index];
if (item.type == SubgroupItemType.group || item.type == SubgroupItemType.subgroup) {
final params = <String, dynamic>{'groupId': item.id, 'name': item.name, 'relativePath': item.relativePath};
Navigator.of(context).pushNamed(SubgroupPage.routeName, arguments: params);
} else {
final params = <String, dynamic>{'projectId': item.id, 'name': item.name, 'relativePath': item.relativePath, "groupId": item.parentId};
Navigator.of(context).pushNamed(ProjectIssuesPage.routeName, arguments: params);
}
},
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
),
height: 80,
padding: const EdgeInsets.only(left: 12, right: 12),
child: SizedBox(
height: 79,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
data[index].icon,
width: 16,
height: 16,
),
const SizedBox(width: 12),
Text(
data[index].name,
style: textTheme.titleLarge,
),
const Spacer(
flex: 1,
),
const Icon(
Icons.keyboard_arrow_right,
color: Colors.grey,
)
],
),
const Icon(
Icons.keyboard_arrow_right,
color: Colors.grey,
)
],
),
),
),
),
),
));
}
));
});
}
}
......@@ -338,6 +338,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.0"
get_it:
dependency: "direct main"
description:
name: get_it
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.2.0"
glob:
dependency: transitive
description:
......
......@@ -49,6 +49,7 @@ dependencies:
permission_handler: ^10.2.0
# For querying information about an application package
package_info_plus: ^3.0.2
get_it: ^7.2.0
dev_dependencies:
......
import 'package:flutter_test/flutter_test.dart';
import 'package:jihu_gitlab_app/core/list_comparator.dart';
import 'package:jihu_gitlab_app/modules/todo_list/widgets/member_entity.dart';
void main() {
test("Should be able to compare list", () {
List<MemberEntity> oldValue = [
MemberEntity.create({"id": 2, "name": "name2", "username": "username2", "avatar_url": "avatar_url_2"}, 59893, 1)
];
List<MemberEntity> newValue = [
MemberEntity.create({"id": 1, "name": "name1", "username": "username1", "avatar_url": "avatar_url_1"}, 59893, 1),
MemberEntity.create({"id": 2, "name": "name2_new", "username": "username2_new", "avatar_url": "avatar_url_2_new"}, 59893, 1)
];
var compareResult = ListComparator.compare<MemberEntity>(oldValue, newValue);
expect(compareResult.hasAdd, true);
expect(compareResult.hasUpdate, true);
expect(compareResult.add.length, 1);
expect(compareResult.add[0].memberId, 1);
expect(compareResult.update.length, 1);
expect(compareResult.update[0].left.toMap(), {"id": null, "member_id": 2, "name": "name2", "username": "username2", "avatar_url": "avatar_url_2", "project_id": 59893, "version": 1});
expect(compareResult.update[0].right.toMap(), {"id": null, "member_id": 2, "name": "name2_new", "username": "username2_new", "avatar_url": "avatar_url_2_new", "project_id": 59893, "version": 1});
});
}
import 'package:flutter_test/flutter_test.dart';
import 'package:jihu_gitlab_app/core/net/http_client.dart';
import 'package:jihu_gitlab_app/core/net/response.dart';
import 'package:jihu_gitlab_app/core/synchronizer.dart';
import 'package:mockito/mockito.dart';
import 'net/http_request_test.mocks.dart';
void main() {
test("Should be able to sync data", () async {
List<Map<String, Object>> allData = [];
int counter = 0;
MockHttpClient client = MockHttpClient();
HttpClient.setInstance(client);
when(client.get<List<Map<String, Object>>>("http://remote-data-repository?page=1&per_page=1")).thenAnswer((_) => Future(() => Response.of<List<Map<String, Object>>>([
{"id": 1}
])));
when(client.get<List<Map<String, Object>>>("http://remote-data-repository?page=2&per_page=1")).thenAnswer((_) => Future(() => Response.of<List<Map<String, Object>>>([
{"id": 2}
])));
when(client.get<List<Map<String, Object>>>("http://remote-data-repository?page=3&per_page=1")).thenAnswer((_) => Future(() => Response.of<List<Map<String, Object>>>([])));
var result = await Synchronizer<List<Map<String, Object>>>(
url: "http://remote-data-repository",
pageSize: 1,
dataProcessor: (data, page, pageSize) {
allData.addAll(data);
counter++;
return Future(() => counter < 3);
}).run();
expect(result, true);
expect(allData.length, 2);
expect(allData[0], {"id": 1});
expect(allData[1], {"id": 2});
});
}
// Mocks generated by Mockito 5.3.2 from annotations
// in jihu_gitlab_app/test/core/widgets/selector/member_provider_test.dart.
// in jihu_gitlab_app/test/core/widgets/user_selector/member_provider_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i3;
import 'package:jihu_gitlab_app/modules/todo_list/widgets/member_repository.dart' as _i2;
import 'package:jihu_gitlab_app/modules/todo_list/widgets/member_entity.dart' as _i4;
import 'package:jihu_gitlab_app/modules/todo_list/widgets/member_repository.dart' as _i2;
import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: type=lint
......
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jihu_gitlab_app/core/dependency_injector.dart';
import 'package:jihu_gitlab_app/core/local_storage.dart';
import 'package:jihu_gitlab_app/core/net/http_client.dart';
import 'package:jihu_gitlab_app/core/net/response.dart';
import 'package:jihu_gitlab_app/core/token_provider.dart';
import 'package:jihu_gitlab_app/core/user_provider.dart';
import 'package:jihu_gitlab_app/modules/issues/description_selector.dart';
import 'package:jihu_gitlab_app/modules/issues/issue_creation_page.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/subgroup_page.dart';
......@@ -18,7 +20,12 @@ import '../mocker/tester.dart';
final client = MockHttpClient();
void main() {
setupLocator();
UserProvider().reset(Tester.user());
testWidgets('Should sub group page can create issue', (WidgetTester tester) async {
HttpClient.setInstance(client);
when(client.get<List<dynamic>>('/api/v4/groups/1?all_available=true&page=1&per_page=50')).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([])));
when(client.get<List<dynamic>>('/api/v4/groups/1/subgroups?all_available=true&page=1&per_page=50')).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([])));
TokenProvider().reset(Tester.token());
when(client.get<Map<String, dynamic>>("/api/v4/user")).thenAnswer((_) => Future(() => Response.of<Map<String, dynamic>>({'id': 5882})));
HttpClient.setInstance(client);
......@@ -74,6 +81,8 @@ void main() {
testWidgets('Should user create issue cancelled', (WidgetTester tester) async {
HttpClient.setInstance(client);
when(client.get<List<dynamic>>('/api/v4/groups/1?all_available=true&page=1&per_page=50')).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([])));
when(client.get<List<dynamic>>('/api/v4/groups/1/subgroups?all_available=true&page=1&per_page=50')).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([])));
TokenProvider().reset(Tester.token());
......@@ -184,8 +193,8 @@ void main() {
await LocalStorage.init();
await TokenProvider().restore();
when(client.get<List<dynamic>>("/api/v4/groups?top_level_only=true&page=1&per_page=20")).thenAnswer((_) => Future(() => Response.of<List<dynamic>>(groups)));
when(client.get<List<dynamic>>('/api/v4/groups/118014/subgroups?page=1&per_page=200&all_available=true')).thenAnswer((_) => Future(() => Response.of<List<dynamic>>(groups)));
when(client.get<dynamic>('/api/v4/groups/118014?page=1&per_page=200&all_available=true')).thenAnswer((_) => Future(() => Response.of<dynamic>({'projects': []})));
when(client.get<List<dynamic>>('/api/v4/groups/118014/subgroups?all_available=true&page=1&per_page=50')).thenAnswer((_) => Future(() => Response.of<List<dynamic>>(groups)));
when(client.get<dynamic>('/api/v4/groups/118014?all_available=true&page=1&per_page=50')).thenAnswer((_) => Future(() => Response.of<dynamic>({'projects': []})));
when(client.post<List<dynamic>>("/api/graphql", [
{
......
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jihu_gitlab_app/core/dependency_injector.dart';
import 'package:jihu_gitlab_app/core/local_storage.dart';
import 'package:jihu_gitlab_app/core/net/http_client.dart';
import 'package:jihu_gitlab_app/core/net/response.dart';
import 'package:jihu_gitlab_app/core/token_provider.dart';
import 'package:jihu_gitlab_app/core/user_provider.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project_provider.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/subgroups/project/project_issues_page.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/subgroups/subgroup_list_model.dart';
import 'package:jihu_gitlab_app/modules/projects/group_details/subgroups/subgroup_list_page.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../core/net/http_request_test.mocks.dart';
import '../mocker/tester.dart';
import 'go_into_sub_group_page_test.mocks.dart';
final client = MockHttpClient();
@GenerateNiceMocks([MockSpec<GroupAndProjectProvider>()])
void main() {
var subgroupListModel = SubgroupListModel();
locator.registerSingleton(subgroupListModel);
var provider = MockGroupAndProjectProvider();
when(provider.loadFromLocal()).thenAnswer((_) => Future(() => [GroupAndProject(59893, "极狐 GitLab APP 代码", SubgroupItemType.project, "ultimate-plan/jihu-gitlab-app/jihu-gitlab-app", 0)]));
subgroupListModel.injectDataProviderForTesting(provider);
testWidgets('Should go in to project page', (WidgetTester tester) async {
SharedPreferences.setMockInitialValues(<String, Object>{});
await LocalStorage.init();
await TokenProvider().reset(Tester.token());
UserProvider().reset(Tester.user());
when(client.get<List<dynamic>>('/api/v4/groups/0/subgroups?page=1&per_page=200&all_available=true')).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([])));
when(client.get<dynamic>('/api/v4/groups/0?page=1&per_page=200&all_available=true')).thenAnswer((_) => Future(() => Response.of<dynamic>({
'projects': [
{
"id": 59893,
"name": "极狐 GitLab APP 代码",
"description": "",
"visibility": "public",
"full_name": "旗舰版演示 / 极狐 GitLab App 产品线 / 极狐 GitLab APP 代码",
"created_at": "2022-10-10T15:44:13.763+08:00",
"updated_at": "2022-12-01T17:22:40.513+08:00",
"avatar_url": null,
"type": "project",
"can_edit": false,
"edit_path": "/ultimate-plan/jihu-gitlab-app/jihu-gitlab-app/edit",
"path_with_namespace": "/ultimate-plan/jihu-gitlab-app/jihu-gitlab-app",
"permission": null,
"last_activity_at": "2022-12-01T17:22:40.513+08:00",
"star_count": 2,
"archived": false,
"markdown_description": "",
"marked_for_deletion": false,
"compliance_management_framework": null
}
]
})));
when(provider.syncFromRemote()).thenAnswer((_) => Future(() => false));
when(client.get<List<dynamic>>('/api/v4/projects/59893/issues?page=1&per_page=20')).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([
{
"id": 287556,
......@@ -123,10 +112,7 @@ void main() {
ProjectIssuesPage.routeName: (context) => const ProjectIssuesPage(arguments: {"name": "极狐 GitLab APP 代码", "projectId": 59893, "relativePath": "relativePath"})
},
home: const Scaffold(
body: SubgroupListPage(
relativePath: 'relativePath',
groupId: 0,
),
body: SubgroupListPage(relativePath: 'relativePath', groupId: 0),
),
));
......
// Mocks generated by Mockito 5.3.2 from annotations
// in jihu_gitlab_app/test/integration_tests/go_into_sub_group_page_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i3;
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project.dart' as _i4;
import 'package:jihu_gitlab_app/modules/projects/group_details/group_and_project_provider.dart' as _i2;
import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
/// A class which mocks [GroupAndProjectProvider].
///
/// See the documentation for Mockito's code generation for more information.
class MockGroupAndProjectProvider extends _i1.Mock implements _i2.GroupAndProjectProvider {
@override
_i3.Future<List<_i4.GroupAndProject>> loadFromLocal() => (super.noSuchMethod(
Invocation.method(
#loadFromLocal,
[],
),
returnValue: _i3.Future<List<_i4.GroupAndProject>>.value(<_i4.GroupAndProject>[]),
returnValueForMissingStub: _i3.Future<List<_i4.GroupAndProject>>.value(<_i4.GroupAndProject>[]),
) as _i3.Future<List<_i4.GroupAndProject>>);
@override
_i3.Future<bool> syncFromRemote() => (super.noSuchMethod(
Invocation.method(
#syncFromRemote,
[],
),
returnValue: _i3.Future<bool>.value(false),
returnValueForMissingStub: _i3.Future<bool>.value(false),
) as _i3.Future<bool>);
}
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册