diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 66228370f1eddb0d5503f135e467a40925c108bb..7d85bfccd13014544c189fefc0c9deef6d58d3c0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -15,15 +15,13 @@ PODS: - Flutter - package_info_plus (0.4.5): - Flutter - - path_provider_foundation (0.0.1): + - path_provider_ios (0.0.1): - Flutter - - FlutterMacOS - permission_handler_apple (9.0.4): - Flutter - ReachabilitySwift (5.0.0) - - shared_preferences_foundation (0.0.1): + - shared_preferences_ios (0.0.1): - Flutter - - FlutterMacOS - sqflite (0.0.2): - Flutter - FMDB (>= 2.7.5) @@ -46,9 +44,9 @@ DEPENDENCIES: - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - open_filex (from `.symlinks/plugins/open_filex/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) + - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) + - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - updater (from `.symlinks/plugins/updater/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -75,12 +73,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/open_filex/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/ios" + path_provider_ios: + :path: ".symlinks/plugins/path_provider_ios/ios" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/ios" + shared_preferences_ios: + :path: ".symlinks/plugins/shared_preferences_ios/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" updater: @@ -102,14 +100,14 @@ SPEC CHECKSUMS: image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 + path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 - shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca + shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 updater: d6c70e66a13a3704329f41f5653dd6e503753fa5 - url_launcher_ios: fb12c43172927bb5cf75aeebd073f883801f1993 + url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f diff --git a/lib/core/widgets/input_field_with_title.dart b/lib/core/widgets/input_field_with_title.dart index 6a1d2d3f338efd6264a3d992e463b4c2842497be..734b3ed50041d0851133d7d9492830659534b1ca 100644 --- a/lib/core/widgets/input_field_with_title.dart +++ b/lib/core/widgets/input_field_with_title.dart @@ -5,8 +5,9 @@ class InputFieldWithTitle extends StatefulWidget { final String placeHolder; final TextEditingController controller; final int? maxLines; + final ValueChanged<String>? onChanged; - const InputFieldWithTitle({required this.title, required this.placeHolder, required this.controller, this.maxLines = 1, Key? key}) : super(key: key); + const InputFieldWithTitle({required this.title, required this.placeHolder, required this.controller, this.maxLines = 1, this.onChanged, Key? key}) : super(key: key); @override State<StatefulWidget> createState() => _InputFieldWithTitleState(); @@ -31,6 +32,7 @@ class _InputFieldWithTitleState extends State<InputFieldWithTitle> { style: const TextStyle(color: Color(0xFF03162F), fontSize: 14, fontWeight: FontWeight.w400), maxLines: widget.maxLines, cursorColor: const Color(0xFFFC6D26), + onChanged: widget.onChanged, decoration: InputDecoration( hintText: widget.placeHolder, hintStyle: const TextStyle(fontSize: 14), diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b63c7f0a6ac71f51928f47030cc2fade8ada3d48..d76f5a985a05c3a8a158ba6fd4852b83c7fa4cd8 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -252,5 +252,6 @@ "in_discussions_and_create_posts": "in discussions and create posts", "other_connection_not_allow": "Other connect methods are only support to view", "to_connect": "To Connect", - "post": "Post" + "post": "Post", + "faqType": "Type (required)" } diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index db3eed9ca2a48efae26b7116dfa2e250cc969537..6c7119b14f4c63e4b7a3a654b03e7b5c665186d5 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -252,5 +252,7 @@ "in_discussions_and_create_posts": "或创建帖å", "other_connection_not_allow": "其他连接方å¼ä»…支æŒæµè§ˆ", "to_connect": "连接", - "post": "帖å" + "post": "帖å", + "faqType": "版å—(必填)" + } diff --git a/lib/modules/faq/faq_creation_model.dart b/lib/modules/faq/faq_creation_model.dart index 120a7fbad40edab7c1e691010de77232f70d5cc2..ba24f8fb782f6a0cc37bbe63847f835f4d87dc5c 100644 --- a/lib/modules/faq/faq_creation_model.dart +++ b/lib/modules/faq/faq_creation_model.dart @@ -1,5 +1,10 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; import 'package:jihu_gitlab_app/core/net/http_client.dart'; import 'package:jihu_gitlab_app/core/user_provider/user_provider.dart'; +import 'package:jihu_gitlab_app/modules/faq/faq_model.dart'; +import 'package:jihu_gitlab_app/modules/faq/faq_type_repo.dart'; import 'package:jihu_gitlab_app/modules/issues/manage/models/issue_draft_entity.dart'; import 'package:jihu_gitlab_app/modules/issues/manage/models/issue_draft_repository.dart'; @@ -8,28 +13,37 @@ import '../../core/net/api.dart'; class FaqCreationModel { late int _projectId; final IssueDraftRepository _repository = IssueDraftRepository(); + final List<FaqType> _faqTypes = []; + late ValueNotifier<List<FaqType>> _notifier; + FaqType? _selectedFaqType; + String? _title; void init(int projectId) { _projectId = projectId; + _notifier = ValueNotifier(_faqTypes); + FaqTypeRepo.instance().getFaqTypes().then((faqTypes) { + _faqTypes.addAll(faqTypes); + _notifier.value = List.of(_faqTypes); + }); } - Future<bool> create(int projectId, String title, {String? description, String? labels}) async { + Future<bool> create(int projectId, String title, Locale locale, {String? description, String? labels}) async { try { String uri = Api().jihu().join('/projects/$projectId/issues'); var body = <String, dynamic>{'title': title}; if (description != null) { body['description'] = description; } + body['labels'] = [_selectedFaqType!.name, locale == const Locale("zh") ? "lang::zh" : "lang::en"].join(","); await HttpClient.instance().postWithHeader<Map<String, dynamic>>(uri, body, ConnectionProvider().jiHuConnection!.authHeaders); _repository.delete(projectId, ConnectionProvider().jiHuConnection!.userInfo.id); + _reset(); return Future.value(true); } catch (error) { return Future.value(false); } } - int get projectId => _projectId; - void saveAsDraft(String title, String description) async { var userId = ConnectionProvider().jiHuConnection!.userInfo.id; var faqDraft = { @@ -51,4 +65,25 @@ class FaqCreationModel { List<IssueDraftEntity> issueDrafts = await _repository.query(projectId, ConnectionProvider().jiHuConnection!.userInfo.id); return issueDrafts; } + + void selectFaqType(FaqType faqType) { + _selectedFaqType = faqType; + } + + bool isSelected(FaqType faqType) { + return _selectedFaqType == faqType; + } + + void _reset() { + _selectedFaqType = null; + _title = null; + } + + void changeTitle(String text) => _title = text; + + int get projectId => _projectId; + + ValueNotifier<List<FaqType>> get notifier => _notifier; + + bool get submittable => _selectedFaqType != null && _title != null && _title!.isNotEmpty; } diff --git a/lib/modules/faq/faq_creation_page.dart b/lib/modules/faq/faq_creation_page.dart index 8dc2b7eea678425930220b5e7d2484a5b7321e67..7c809d07a6e8aab3ac42396fc7ed8f44f13abf9b 100644 --- a/lib/modules/faq/faq_creation_page.dart +++ b/lib/modules/faq/faq_creation_page.dart @@ -7,9 +7,11 @@ import 'package:jihu_gitlab_app/core/widgets/common_app_bar.dart'; import 'package:jihu_gitlab_app/core/widgets/connect_jihu_page.dart'; import 'package:jihu_gitlab_app/core/widgets/input_field_with_title.dart'; import 'package:jihu_gitlab_app/core/widgets/markdown/markdown_input_box.dart'; +import 'package:jihu_gitlab_app/core/widgets/streaming_container.dart'; import 'package:jihu_gitlab_app/core/widgets/toast.dart'; import 'package:jihu_gitlab_app/l10n/jihu_localizations.dart'; import 'package:jihu_gitlab_app/modules/faq/faq_creation_model.dart'; +import 'package:jihu_gitlab_app/modules/faq/faq_model.dart'; import 'package:provider/provider.dart'; class FaqCreationPage extends StatefulWidget { @@ -71,21 +73,23 @@ class _FaqCreationPageState extends State<FaqCreationPage> { Container( transform: Matrix4.translationValues(-12, 2, 0), child: TextButton( - onPressed: () { - var title = _issueTitleController.text; - var description = _issueDescriptionController.text; - Loader.showProgress(context); - _model.create(widget.arguments['projectId'], title, description: description).then((value) { - Loader.hideProgress(context); - if (value) { - Toast.success(context, JiHuLocalizations.dictionary().createSuccessful); - Navigator.pop(context, true); - } else { - Toast.error(context, JiHuLocalizations.dictionary().createFailed); - } - }); - }, - child: Text(JiHuLocalizations.dictionary().create, style: TextStyle(color: Theme.of(context).primaryColor))), + onPressed: _model.submittable + ? () { + var title = _issueTitleController.text; + var description = _issueDescriptionController.text; + Loader.showProgress(context); + _model.create(widget.arguments['projectId'], title, description: description, Localizations.localeOf(context)).then((value) { + Loader.hideProgress(context); + if (value) { + Toast.success(context, JiHuLocalizations.dictionary().createSuccessful); + Navigator.pop(context, true); + } else { + Toast.error(context, JiHuLocalizations.dictionary().createFailed); + } + }); + } + : null, + child: Text(JiHuLocalizations.dictionary().create, style: TextStyle(color: Theme.of(context).primaryColor.withOpacity(_model.submittable ? 1 : 0.5)))), ), ], ), @@ -94,9 +98,16 @@ class _FaqCreationPageState extends State<FaqCreationPage> { child: SafeArea( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ InputFieldWithTitle( - title: JiHuLocalizations.dictionary().createIssueTitle, - placeHolder: JiHuLocalizations.dictionary().issueTitlePlaceholder(JiHuLocalizations.dictionary().post), - controller: _issueTitleController), + title: JiHuLocalizations.dictionary().createIssueTitle, + placeHolder: JiHuLocalizations.dictionary().issueTitlePlaceholder(JiHuLocalizations.dictionary().post), + controller: _issueTitleController, + onChanged: (text) { + _model.changeTitle(text); + setState(() {}); + }, + ), + _buildTitle(JiHuLocalizations.dictionary().faqType), + _buildFaqTypeSelector(), _buildTitle(JiHuLocalizations.dictionary().description), Container( margin: const EdgeInsets.symmetric(horizontal: 16), @@ -129,4 +140,32 @@ class _FaqCreationPageState extends State<FaqCreationPage> { setState(() {}); } } + + Widget _buildFaqTypeSelector() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ValueListenableBuilder<List<FaqType>>( + valueListenable: _model.notifier, + builder: (context, data, child) => StreamingContainer(children: data.map((e) => _buildFaqTypeRadio(e)).toList()), + ), + ); + } + + Widget _buildFaqTypeRadio(FaqType faqType) { + return InkWell( + onTap: () => setState(() => _model.selectFaqType(faqType)), + child: Stack( + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration(color: _model.isSelected(faqType) ? const Color(0xFFFFEBE2) : Colors.white, borderRadius: const BorderRadius.all(Radius.circular(4))), + child: Text( + faqType.typeName(Localizations.localeOf(context)), + style: TextStyle(color: Color(_model.isSelected(faqType) ? 0xFFFC6D26 : 0xFF03162F), fontSize: 13, fontWeight: FontWeight.w400), + ), + ) + ], + ), + ); + } } diff --git a/lib/modules/faq/faq_model.dart b/lib/modules/faq/faq_model.dart index a3cf139f496df7fc7f912729b4ab6a730c21128a..9325ff6e7f77609613d8269daf9dd12c50ea3305 100644 --- a/lib/modules/faq/faq_model.dart +++ b/lib/modules/faq/faq_model.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:jihu_gitlab_app/core/id.dart'; import 'package:jihu_gitlab_app/core/net/api.dart'; import 'package:jihu_gitlab_app/core/net/http_client.dart'; +import 'package:jihu_gitlab_app/core/user_provider/user_provider.dart'; import 'package:jihu_gitlab_app/modules/faq/faq.dart'; +import 'package:jihu_gitlab_app/modules/faq/faq_type_repo.dart'; import 'package:jihu_gitlab_app/modules/issues/manage/widgets/label.dart'; class FaqType extends Label { @@ -28,7 +30,6 @@ class FaqType extends Label { class FaqModel { static const int _faqProjectId = 89335; static const String _faqRelativePath = 'ultimate-plan/jihu-gitlab-app/faq'; - static const String _faqTypePrefix = 'type'; late ValueNotifier<List<FaqType>> _notifier; final List<FaqType> _faqTypes = [FaqType.all]; late TabController _tabController; @@ -37,35 +38,19 @@ class FaqModel { Future<void> init(TickerProvider tickerProvider) { _notifier = ValueNotifier(_faqTypes); _tabController = TabController(length: _faqTypes.length, vsync: tickerProvider); - return _getFaqTypes().then((value) { + return FaqTypeRepo.instance().getFaqTypes().then((value) { _faqTypes.addAll(value); _tabController = TabController(length: _faqTypes.length, vsync: tickerProvider); _notifier.value = List.of(_faqTypes); }); } - Future<List<FaqType>> _getFaqTypes() async { - var response = await HttpClient.instance().post(Api().jihu().graphQL(), { - "query": """ - { - project(fullPath:"$_faqRelativePath"){ - labels{ - nodes{ - id - title - description - } - } - } - } - """ + Future<bool> checkUserIsProjectMember() { + if (ConnectionProvider().jiHuConnection == null) return Future.value(false); + return HttpClient.instance().get(Api().jihu().join("projects/$_faqProjectId")).then((response) { + int? accessLevel = response.body()["permissions"]?['project_access']?['access_level']; + return Future(() => accessLevel != null && accessLevel > 0); }); - return ((response.body()['data']?['project']?['labels']?['nodes'] ?? []) as List) - .map((e) => FaqType.fromGraphqlJson(e)) - .where((e) => e.name.isNotEmpty) - .where((e) => e.name.startsWith(_faqTypePrefix)) - .toSet() - .toList(); } void changeSelectedFaq(Faq? faq) { diff --git a/lib/modules/faq/faq_page.dart b/lib/modules/faq/faq_page.dart index 908806a69e2e39a4885f025f093bdc26aace369c..c82cd6386f28833d9334b154fa7cbaca3a979d51 100644 --- a/lib/modules/faq/faq_page.dart +++ b/lib/modules/faq/faq_page.dart @@ -58,16 +58,28 @@ class _FaqPageState extends State<FaqPage> with TickerProviderStateMixin { Widget _buildListPage() { return HomeScaffold.withAppBar( params: HomeAppBarRequiredParams(title: Text(JiHuLocalizations.dictionary().community), context: context, actions: [ - IconButton( - icon: const Icon(Icons.add_box_outlined), - color: const Color(0xFF87878C), - onPressed: () async { - Navigator.of(context).pushNamed(FaqCreationPage.routeName, arguments: {"projectId": _model.projectId}).then((value) { - if (value != null && value is bool && value == true) { - _refreshKey.currentState?.onRefresh(); - setState(() {}); - } - }); + Consumer<ConnectionProvider>( + builder: (context, _, child) { + return FutureBuilder<bool>( + initialData: false, + future: _model.checkUserIsProjectMember(), + builder: (context, snapshot) { + return (snapshot.data ?? false) + ? IconButton( + icon: const Icon(Icons.add_box_outlined), + color: const Color(0xFF87878C), + onPressed: () async { + Navigator.of(context).pushNamed(FaqCreationPage.routeName, arguments: {"projectId": _model.projectId}).then((value) { + if (value != null && value is bool && value == true) { + _refreshKey.currentState?.onRefresh(); + setState(() {}); + } + }); + }, + ) + : const SizedBox(); + }, + ); }, ) ]), diff --git a/lib/modules/faq/faq_type_repo.dart b/lib/modules/faq/faq_type_repo.dart new file mode 100644 index 0000000000000000000000000000000000000000..fa03f3b1599f53f02332ebef69442157542ec0ae --- /dev/null +++ b/lib/modules/faq/faq_type_repo.dart @@ -0,0 +1,38 @@ +import 'package:jihu_gitlab_app/core/net/api.dart'; +import 'package:jihu_gitlab_app/core/net/http_client.dart'; +import 'package:jihu_gitlab_app/modules/faq/faq_model.dart'; + +class FaqTypeRepo { + static const String _faqRelativePath = 'ultimate-plan/jihu-gitlab-app/faq'; + static const String _faqTypePrefix = 'type'; + + static final FaqTypeRepo _instance = FaqTypeRepo._internal(); + + FaqTypeRepo._internal(); + + factory FaqTypeRepo.instance() => _instance; + + Future<List<FaqType>> getFaqTypes() async { + var response = await HttpClient.instance().post(Api().jihu().graphQL(), { + "query": """ + { + project(fullPath:"$_faqRelativePath"){ + labels{ + nodes{ + id + title + description + } + } + } + } + """ + }); + return ((response.body()['data']?['project']?['labels']?['nodes'] ?? []) as List) + .map((e) => FaqType.fromGraphqlJson(e)) + .where((e) => e.name.isNotEmpty) + .where((e) => e.name.startsWith(_faqTypePrefix)) + .toSet() + .toList(); + } +} diff --git a/lib/modules/issues/manage/models/issue_draft_entity.dart b/lib/modules/issues/manage/models/issue_draft_entity.dart index d6279e8f609e05788c62bccd1aeb9de374ca20e0..a8b6bdc1020e9542b8f18d28fd482637662c840d 100644 --- a/lib/modules/issues/manage/models/issue_draft_entity.dart +++ b/lib/modules/issues/manage/models/issue_draft_entity.dart @@ -19,8 +19,8 @@ class IssueDraftEntity { IssueDraftEntity._internal(this.userId, this.projectId, this.title, this.description, this.template, this.assignees, this.labels, this.confidential, this.version); static IssueDraftEntity restore(Map<String, dynamic> map) { - List<Member> members = !map.containsKey('selected_assignees') ? [] : (jsonDecode(map['selected_assignees']) as List).map((e) => Member.fromJson(e)).toList(); - List<Label> labels = !map.containsKey('selected_labels') ? [] : (jsonDecode(map['selected_labels']) as List).map((e) => Label.fromJson(e)).toList(); + List<Member> members = !map.containsKey('selected_assignees') ? [] : ((jsonDecode(map['selected_assignees']) ?? []) as List).map((e) => Member.fromJson(e)).toList(); + List<Label> labels = !map.containsKey('selected_labels') ? [] : ((jsonDecode(map['selected_labels']) ?? []) as List).map((e) => Label.fromJson(e)).toList(); return IssueDraftEntity._internal(map['user_id'], map['project_id'], map['title'], map['description'], map['template'], members, labels, map['confidential'] ?? 0, map['version']); } diff --git a/test/integration_tests/create_faq_test.dart b/test/integration_tests/create_faq_test.dart index efaec318aae7ea73537209b6850ce5c3be9d32e4..c3d894f283b4d8feef53e9ce19edc83dd59b5a34 100644 --- a/test/integration_tests/create_faq_test.dart +++ b/test/integration_tests/create_faq_test.dart @@ -6,7 +6,7 @@ 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' as r; import 'package:jihu_gitlab_app/core/user_provider/user_provider.dart'; -import 'package:jihu_gitlab_app/core/widgets/connect_jihu_page.dart'; +import 'package:jihu_gitlab_app/core/widgets/streaming_container.dart'; import 'package:jihu_gitlab_app/generated/l10n.dart'; import 'package:jihu_gitlab_app/l10n/current_locale.dart'; import 'package:jihu_gitlab_app/modules/faq/faq_creation_page.dart'; @@ -34,12 +34,17 @@ void main() { ConnectionProvider().reset(Tester.jihuLabUser()); ConnectionProvider().injectConnectionForTest(Tester.selfManagedUser()); client = MockHttpClient(); + when(client.get("https://jihulab.com/api/v4/projects/89335")).thenAnswer((_) => Future(() => r.Response.of({ + "permissions": { + "project_access": {"access_level": 50} + } + }))); HttpClient.injectInstanceForTesting(client); }); testWidgets('Should create faq', (WidgetTester tester) async { setUpMobileBinding(tester); - when(client.post('https://jihulab.com/api/graphql', any)).thenAnswer((_) => Future(() => r.Response.of(faqListResponse))); + when(client.post('https://jihulab.com/api/graphql', argThat(predicate((arg) => arg.toString().contains("labels"))))).thenAnswer((_) => Future(() => r.Response.of(faqTypesResponse))); when(client.postWithHeader<Map<String, dynamic>>('https://jihulab.com/api/v4/projects/89335/issues', any, any)).thenAnswer((_) => Future(() => r.Response.of<Map<String, dynamic>>({}))); HttpClient.injectInstanceForTesting(client); @@ -58,6 +63,8 @@ void main() { await tester.pumpAndSettle(); expect(find.byType(FaqCreationPage), findsOneWidget); expect(find.text("Title (required)"), findsOneWidget); + expect(find.text("Type (required)"), findsOneWidget); + expect(find.byType(StreamingContainer), findsOneWidget); expect(find.text("Write a Post title"), findsOneWidget); expect(find.text("Description"), findsOneWidget); expect(find.text("Write a description here..."), findsOneWidget); @@ -65,11 +72,20 @@ void main() { expect(SvgFinder("assets/images/photo_picker.svg"), findsOneWidget); expect(SvgFinder("assets/images/numbers.svg"), findsNothing); expect(SvgFinder("assets/images/alternate_email.svg"), findsNothing); + expect(find.text("FAQ"), findsOneWidget); + expect(find.text("WFH"), findsOneWidget); + var buttonFinder = find.widgetWithText(TextButton, "Create"); + expect(buttonFinder, findsOneWidget); + expect(tester.firstWidget<TextButton>(buttonFinder).onPressed, null); + await tester.enterText(find.byWidgetPredicate((widget) => widget is TextField && widget.decoration?.hintText == 'Write a Post title'), 'example'); await tester.pumpAndSettle(); await tester.enterText(find.byWidgetPredicate((widget) => widget is TextField && widget.decoration?.hintText == 'Write a description here...'), 'example'); await tester.pumpAndSettle(); - await tester.tap(find.byType(TextButton)); + await tester.tap(find.text("FAQ")); + await tester.pumpAndSettle(); + expect(tester.firstWidget<TextButton>(buttonFinder).onPressed, predicate((value) => value != null)); + await tester.tap(buttonFinder); await tester.pumpAndSettle(const Duration(seconds: 2)); expect(find.byType(FaqPage), findsOneWidget); }); @@ -132,7 +148,7 @@ void main() { await tester.tap(find.byIcon(Icons.add_box_outlined)); }); - testWidgets('Should see to connect page when no login account', (WidgetTester tester) async { + testWidgets('Should not see the connect page when no login account', (WidgetTester tester) async { setUpMobileBinding(tester); ConnectionProvider().loggedOut(true); ConnectionProvider().clearActive(); @@ -150,29 +166,33 @@ void main() { )); await tester.pumpAndSettle(const Duration(seconds: 1)); - expect(find.byIcon(Icons.add_box_outlined), findsOneWidget); - await tester.tap(find.byIcon(Icons.add_box_outlined)); - await tester.pumpAndSettle(); - expect(find.byType(ConnectJihuPage), findsOneWidget); - expect(find.textContaining("Connect with jihulab.com to participate"), findsOneWidget); - await tester.tap(find.text("To Connect")); - await tester.pumpAndSettle(); - ConnectionProvider().reset(Tester.jihuLabUser()); - await tester.tap(find.byType(BackButton)); - await tester.pumpAndSettle(); - expect(find.byType(FaqCreationPage), findsOneWidget); - expect(find.text("Title (required)"), findsOneWidget); - expect(find.text("Write a Post title"), findsOneWidget); - expect(find.text("Description"), findsOneWidget); - expect(find.text("Write a description here..."), findsOneWidget); - expect(SvgFinder("assets/images/video_picker.svg"), findsOneWidget); - expect(SvgFinder("assets/images/photo_picker.svg"), findsOneWidget); - expect(SvgFinder("assets/images/numbers.svg"), findsNothing); - expect(SvgFinder("assets/images/alternate_email.svg"), findsNothing); + expect(find.byIcon(Icons.add_box_outlined), findsNothing); + }); + + testWidgets('Should not see the connect page when user is not the project member', (WidgetTester tester) async { + setUpMobileBinding(tester); + when(client.post('https://jihulab.com/api/graphql', any)).thenAnswer((_) => Future(() => r.Response.of(faqListResponse))); + when(client.get("https://jihulab.com/api/v4/projects/89335")).thenAnswer((_) => Future(() => r.Response.of({ + "permissions": {"project_access": null} + }))); + HttpClient.injectInstanceForTesting(client); + + await tester.pumpWidget(MultiProvider( + providers: [ChangeNotifierProvider(create: (context) => ConnectionProvider()), ChangeNotifierProvider(create: (context) => LocaleProvider())], + child: MaterialApp( + onGenerateRoute: onGenerateRoute, + home: const Scaffold(body: FaqPage()), + localizationsDelegates: const [GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, S.delegate], + ), + )); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + expect(find.byIcon(Icons.add_box_outlined), findsNothing); }); tearDown(() { ConnectionProvider().fullReset(); + reset(client); }); } @@ -190,3 +210,24 @@ Map<String, dynamic> faqListResponse = { } } }; + +Map<String, dynamic> faqTypesResponse = { + "data": { + "project": { + "labels": { + "nodes": [ + { + "id": "gid://gitlab/ProjectLabel/72395", + "title": "type::FAQ", + "description": "é—®ç”", + }, + { + "id": "gid://gitlab/ProjectLabel/72059", + "title": "type::WFH", + "description": "远程办公", + }, + ] + } + } + } +}; diff --git a/test/integration_tests/modules/merge_request/launch_app_and_reopen_merge_request_test.dart b/test/integration_tests/modules/merge_request/launch_app_and_reopen_merge_request_test.dart index 11da6176fe9b758ccf18ad922bb2dc2b60c87617..562c38c4c685c9f7ac27b90d95592cdf942e0a10 100644 --- a/test/integration_tests/modules/merge_request/launch_app_and_reopen_merge_request_test.dart +++ b/test/integration_tests/modules/merge_request/launch_app_and_reopen_merge_request_test.dart @@ -38,6 +38,11 @@ void main() { when(client.get<List<dynamic>>("/api/v4/todos?page=1&per_page=20&state=pending")).thenAnswer((_) => Future(() => Response.of<List<dynamic>>(todoPendingResponseData))); when(client.get<List<dynamic>>("/api/v4/projects/72936/issues/6/discussions?page=1&per_page=50")).thenAnswer((_) => Future(() => Response.of<List<dynamic>>(discussionsData))); when(client.get<List<dynamic>>("/api/v4/projects/72936/labels")).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([]))); + when(client.get("https://jihulab.com/api/v4/projects/89335")).thenAnswer((_) => Future(() => Response.of({ + "permissions": { + "project_access": {"access_level": 50} + } + }))); HttpClient.injectInstanceForTesting(client); }); diff --git a/test/integration_tests/modules/projects/projects_page_test.dart b/test/integration_tests/modules/projects/projects_page_test.dart index 3fc1b96d4cd92f3f7bab25b7799372682d8989ef..8432e047141f1edfd3ac64b9ea015a5765c92719 100644 --- a/test/integration_tests/modules/projects/projects_page_test.dart +++ b/test/integration_tests/modules/projects/projects_page_test.dart @@ -61,6 +61,11 @@ void main() { when(client.get("/api/v4/users/9527/starred_projects?page=1&per_page=50")).thenAnswer((realInvocation) => Future(() => Response.of([]))); when(client.get<List<dynamic>>("/api/v4/todos?page=1&per_page=20&state=pending")).thenAnswer((realInvocation) => Future(() => Response.of<List<dynamic>>(todoPendingResponseData))); when(client.get<List<dynamic>>("/api/v4/groups?top_level_only=true&page=1&per_page=50")).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([]))); + when(client.get("https://jihulab.com/api/v4/projects/89335")).thenAnswer((_) => Future(() => Response.of({ + "permissions": { + "project_access": {"access_level": 50} + } + }))); HttpClient.injectInstanceForTesting(client); }); diff --git a/test/integration_tests/modules/to_dos/to_dos_page_test.dart b/test/integration_tests/modules/to_dos/to_dos_page_test.dart index 1dbed0a70d6eec0f593ac3cc0c7c20232a1a060a..50639853d4efa63a12bb9aaea3a8b2120dc93d21 100644 --- a/test/integration_tests/modules/to_dos/to_dos_page_test.dart +++ b/test/integration_tests/modules/to_dos/to_dos_page_test.dart @@ -42,6 +42,11 @@ void main() { when(client.get<List<dynamic>>("/api/v4/todos?page=1&per_page=20&state=pending")).thenAnswer((_) => Future(() => Response.of<List<dynamic>>(todoPendingResponseData))); when(client.get<List<dynamic>>("/api/v4/projects/72936/issues/6/discussions?page=1&per_page=50")).thenAnswer((_) => Future(() => Response.of<List<dynamic>>(discussionsData))); when(client.get<List<dynamic>>("/api/v4/projects/72936/labels")).thenAnswer((_) => Future(() => Response.of<List<dynamic>>([]))); + when(client.get("https://jihulab.com/api/v4/projects/89335")).thenAnswer((_) => Future(() => Response.of({ + "permissions": { + "project_access": {"access_level": 50} + } + }))); HttpClient.injectInstanceForTesting(client); }); diff --git a/test/modules/faq/faq_page_test.dart b/test/modules/faq/faq_page_test.dart index 4a80dbe1f71f898c72fabc8ac94474db9abfd81d..5060f6047edf082ec6fb8774af7ba79ca592d1fb 100644 --- a/test/modules/faq/faq_page_test.dart +++ b/test/modules/faq/faq_page_test.dart @@ -48,6 +48,11 @@ void main() { .thenAnswer((_) => Future(() => r.Response.of<Map<String, dynamic>>({}))); when(client.getWithHeader<Map<String, dynamic>>("https://jihulab.com/api/v4/projects/89335", {"PRIVATE-TOKEN": '', "authorization": ''})) .thenAnswer((_) => Future(() => r.Response.of<Map<String, dynamic>>({}))); + when(client.get("https://jihulab.com/api/v4/projects/89335")).thenAnswer((_) => Future(() => r.Response.of({ + "permissions": { + "project_access": {"access_level": 50} + } + }))); HttpClient.injectInstanceForTesting(client); await tester.pumpWidget(MultiProvider(