feat(flutter): 增加 debug 服务地址编辑器
This commit is contained in:
1 parent
eab5d3e230
commit
c259b0b543
9 files changed
+298
-5
No files matched your search
@@ -80,7 +80,12 @@ class AppConfig {
|
|||||||
Future<bool> Function(String baseUrl)? probe,
|
Future<bool> Function(String baseUrl)? probe,
|
||||||
String? fallbackBaseUrl,
|
String? fallbackBaseUrl,
|
||||||
}) async {
|
}) async {
|
||||||
for (final candidate in _probeCandidates) {
|
final candidates = <String>[
|
||||||
|
if (_runtimeBaseUrl.isNotEmpty) _runtimeBaseUrl,
|
||||||
|
for (final candidate in _probeCandidates)
|
||||||
|
if (candidate != _runtimeBaseUrl) candidate,
|
||||||
|
];
|
||||||
|
for (final candidate in candidates) {
|
||||||
final result = probe == null
|
final result = probe == null
|
||||||
? await _probeBaseUrl(candidate)
|
? await _probeBaseUrl(candidate)
|
||||||
: await _runInjectedProbe(candidate, probe);
|
: await _runInjectedProbe(candidate, probe);
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ abstract final class ServerEndpoint {
|
|||||||
preferenceKey,
|
preferenceKey,
|
||||||
normalized,
|
normalized,
|
||||||
);
|
);
|
||||||
|
AppConfig.applyRuntimeBaseUrl(normalized);
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ const TextStyle _debugStyle = TextStyle(
|
|||||||
|
|
||||||
/// 仅 debug 展示当前服务地址与目录请求的原始错误。
|
/// 仅 debug 展示当前服务地址与目录请求的原始错误。
|
||||||
class CatalogDebugInfo extends StatelessWidget {
|
class CatalogDebugInfo extends StatelessWidget {
|
||||||
const CatalogDebugInfo({super.key, required this.error});
|
const CatalogDebugInfo({super.key, required this.error, this.onEditEndpoint});
|
||||||
|
|
||||||
final String? error;
|
final String? error;
|
||||||
|
final VoidCallback? onEditEndpoint;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -41,6 +42,12 @@ class CatalogDebugInfo extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: _debugStyle,
|
style: _debugStyle,
|
||||||
),
|
),
|
||||||
|
if (onEditEndpoint != null)
|
||||||
|
TextButton(
|
||||||
|
key: const Key('catalog-debug-endpoint-edit'),
|
||||||
|
onPressed: onEditEndpoint,
|
||||||
|
child: const Text('设置服务地址'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../config/app_config.dart';
|
||||||
|
import '../config/server_endpoint.dart';
|
||||||
|
|
||||||
|
const String _title = '设置服务地址';
|
||||||
|
const String _save = '保存';
|
||||||
|
const String _cancel = '取消';
|
||||||
|
const String _invalid = '请输入合法地址,例如 http://192.168.1.10:8081';
|
||||||
|
const String _saveFailed = '服务地址保存失败';
|
||||||
|
const String _localNetworkHint = '手机需与电脑同一 Wi-Fi,并在 iOS 设置-隐私与安全性-本地网络 中允许本应用';
|
||||||
|
|
||||||
|
/// Debug 下编辑服务地址,保存后重新探测并加载目录。
|
||||||
|
Future<void> showCatalogEndpointEditor(
|
||||||
|
BuildContext context, {
|
||||||
|
required Future<void> Function() reload,
|
||||||
|
Future<bool> Function(String value)? save,
|
||||||
|
Future<void> Function()? probe,
|
||||||
|
}) async {
|
||||||
|
if (!kDebugMode) return;
|
||||||
|
final value = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => _CatalogEndpointDialog(initialValue: AppConfig.apiBaseUrl),
|
||||||
|
);
|
||||||
|
if (value == null) return;
|
||||||
|
final saveEndpoint = save ?? (input) => ServerEndpoint.save(input);
|
||||||
|
if (!await saveEndpoint(value)) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(const SnackBar(content: Text(_saveFailed)));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await (probe ?? () => AppConfig.probeAndApplyBaseUrl())();
|
||||||
|
await reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CatalogEndpointDialog extends StatefulWidget {
|
||||||
|
const _CatalogEndpointDialog({required this.initialValue});
|
||||||
|
|
||||||
|
final String initialValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_CatalogEndpointDialog> createState() => _CatalogEndpointDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CatalogEndpointDialogState extends State<_CatalogEndpointDialog> {
|
||||||
|
late final TextEditingController _controller = TextEditingController(
|
||||||
|
text: widget.initialValue,
|
||||||
|
);
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submit() {
|
||||||
|
final normalized = ServerEndpoint.normalize(_controller.text);
|
||||||
|
if (normalized == null) {
|
||||||
|
setState(() => _error = _invalid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text(_title),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
key: const Key('catalog-debug-endpoint-input'),
|
||||||
|
controller: _controller,
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
autocorrect: false,
|
||||||
|
onSubmitted: (_) => _submit(),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: '服务地址',
|
||||||
|
hintText: 'http://IP:端口',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Text(
|
||||||
|
_localNetworkHint,
|
||||||
|
key: Key('catalog-debug-endpoint-hint'),
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
),
|
||||||
|
if (_error != null) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
_error!,
|
||||||
|
key: const Key('catalog-debug-endpoint-error'),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text(_cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
key: const Key('catalog-debug-endpoint-save'),
|
||||||
|
onPressed: _submit,
|
||||||
|
child: const Text(_save),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import '../../core/theme/app_theme.dart';
|
|||||||
import '../../core/widgets/app_bottom_nav.dart';
|
import '../../core/widgets/app_bottom_nav.dart';
|
||||||
import '../../core/widgets/app_pull_to_refresh.dart';
|
import '../../core/widgets/app_pull_to_refresh.dart';
|
||||||
import '../../core/widgets/catalog_debug_info.dart';
|
import '../../core/widgets/catalog_debug_info.dart';
|
||||||
|
import '../../core/widgets/catalog_endpoint_editor.dart';
|
||||||
import '../../core/widgets/empty_state.dart';
|
import '../../core/widgets/empty_state.dart';
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../filter/filter_page.dart';
|
import '../filter/filter_page.dart';
|
||||||
@@ -258,7 +259,11 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
actionLabel: l10n.retry,
|
actionLabel: l10n.retry,
|
||||||
onAction: () => unawaited(_retryCategories(categories)),
|
onAction: () => unawaited(_retryCategories(categories)),
|
||||||
footer: kDebugMode
|
footer: kDebugMode
|
||||||
? CatalogDebugInfo(error: categories.debugError ?? error)
|
? CatalogDebugInfo(
|
||||||
|
error: categories.debugError ?? error,
|
||||||
|
onEditEndpoint: () =>
|
||||||
|
unawaited(_editCategoriesEndpoint(categories)),
|
||||||
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -518,6 +523,10 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
return retryCatalogLoad(reload: categories.load);
|
return retryCatalogLoad(reload: categories.load);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _editCategoriesEndpoint(CategoryController categories) {
|
||||||
|
return showCatalogEndpointEditor(context, reload: categories.load);
|
||||||
|
}
|
||||||
|
|
||||||
void _act(DiscoverPageAction action) {
|
void _act(DiscoverPageAction action) {
|
||||||
logic.act(
|
logic.act(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import '../../core/platform/haptics.dart';
|
|||||||
import '../../core/theme/app_theme.dart';
|
import '../../core/theme/app_theme.dart';
|
||||||
import '../../core/widgets/app_bottom_nav.dart';
|
import '../../core/widgets/app_bottom_nav.dart';
|
||||||
import '../../core/widgets/catalog_debug_info.dart';
|
import '../../core/widgets/catalog_debug_info.dart';
|
||||||
|
import '../../core/widgets/catalog_endpoint_editor.dart';
|
||||||
import '../../core/widgets/empty_state.dart';
|
import '../../core/widgets/empty_state.dart';
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../discover/discover_types.dart';
|
import '../discover/discover_types.dart';
|
||||||
@@ -79,6 +80,10 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
return retryCatalogLoad(reload: catalog.load);
|
return retryCatalogLoad(reload: catalog.load);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _editHomeEndpoint(CatalogController catalog) {
|
||||||
|
return showCatalogEndpointEditor(context, reload: catalog.load);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final catalog = context.watch<CatalogController>();
|
final catalog = context.watch<CatalogController>();
|
||||||
@@ -107,6 +112,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
body: HomeFailureView(
|
body: HomeFailureView(
|
||||||
error: catalog.debugError ?? catalog.error,
|
error: catalog.debugError ?? catalog.error,
|
||||||
onRetry: () => unawaited(_retryHome(catalog)),
|
onRetry: () => unawaited(_retryHome(catalog)),
|
||||||
|
onEditEndpoint: () => unawaited(_editHomeEndpoint(catalog)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -134,10 +140,16 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
/// 首页目录加载失败与重试入口。
|
/// 首页目录加载失败与重试入口。
|
||||||
class HomeFailureView extends StatelessWidget {
|
class HomeFailureView extends StatelessWidget {
|
||||||
const HomeFailureView({super.key, this.error, required this.onRetry});
|
const HomeFailureView({
|
||||||
|
super.key,
|
||||||
|
this.error,
|
||||||
|
required this.onRetry,
|
||||||
|
this.onEditEndpoint,
|
||||||
|
});
|
||||||
|
|
||||||
final String? error;
|
final String? error;
|
||||||
final VoidCallback onRetry;
|
final VoidCallback onRetry;
|
||||||
|
final VoidCallback? onEditEndpoint;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -147,7 +159,9 @@ class HomeFailureView extends StatelessWidget {
|
|||||||
title: l10n.catalogError,
|
title: l10n.catalogError,
|
||||||
actionLabel: l10n.retry,
|
actionLabel: l10n.retry,
|
||||||
onAction: onRetry,
|
onAction: onRetry,
|
||||||
footer: kDebugMode ? CatalogDebugInfo(error: error) : null,
|
footer: kDebugMode
|
||||||
|
? CatalogDebugInfo(error: error, onEditEndpoint: onEditEndpoint)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,22 @@ void main() {
|
|||||||
expect(AppConfig.apiBaseUrl, AppConfig.devHostBaseUrl);
|
expect(AppConfig.apiBaseUrl, AppConfig.devHostBaseUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should_probe_explicit_runtime_endpoint_first', () async {
|
||||||
|
const custom = 'http://custom.test:8081';
|
||||||
|
final attempts = <String>[];
|
||||||
|
AppConfig.applyRuntimeBaseUrl(custom);
|
||||||
|
|
||||||
|
await AppConfig.probeAndApplyBaseUrl(
|
||||||
|
probe: (baseUrl) async {
|
||||||
|
attempts.add(baseUrl);
|
||||||
|
return baseUrl == custom;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(attempts, [custom]);
|
||||||
|
expect(AppConfig.apiBaseUrl, custom);
|
||||||
|
});
|
||||||
|
|
||||||
test('should_apply_stored_url_only_when_debug_is_enabled', () async {
|
test('should_apply_stored_url_only_when_debug_is_enabled', () async {
|
||||||
SharedPreferencesAsyncPlatform.instance =
|
SharedPreferencesAsyncPlatform.instance =
|
||||||
InMemorySharedPreferencesAsync.withData({
|
InMemorySharedPreferencesAsync.withData({
|
||||||
@@ -139,6 +155,7 @@ void main() {
|
|||||||
await SharedPreferencesAsync().getString(ServerEndpoint.preferenceKey),
|
await SharedPreferencesAsync().getString(ServerEndpoint.preferenceKey),
|
||||||
'http://192.168.31.187:8081',
|
'http://192.168.31.187:8081',
|
||||||
);
|
);
|
||||||
|
expect(AppConfig.apiBaseUrl, 'http://192.168.31.187:8081');
|
||||||
|
|
||||||
await ServerEndpoint.clear();
|
await ServerEndpoint.clear();
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ void main() {
|
|||||||
expect(find.text('Retry'), findsOneWidget);
|
expect(find.text('Retry'), findsOneWidget);
|
||||||
expect(find.byKey(const Key('catalog-debug-endpoint')), findsOneWidget);
|
expect(find.byKey(const Key('catalog-debug-endpoint')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('catalog-debug-error')), findsOneWidget);
|
expect(find.byKey(const Key('catalog-debug-error')), findsOneWidget);
|
||||||
|
expect(
|
||||||
|
find.byKey(const Key('catalog-debug-endpoint-edit')),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
expect(find.textContaining('discover feed unavailable'), findsOneWidget);
|
expect(find.textContaining('discover feed unavailable'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
// Author: fruit.com.cn
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:fruitshorts/core/config/app_config.dart';
|
import 'package:fruitshorts/core/config/app_config.dart';
|
||||||
import 'package:fruitshorts/core/data/services/catalog_retry.dart';
|
import 'package:fruitshorts/core/data/services/catalog_retry.dart';
|
||||||
import 'package:fruitshorts/core/theme/app_colors.dart';
|
import 'package:fruitshorts/core/theme/app_colors.dart';
|
||||||
|
import 'package:fruitshorts/core/widgets/catalog_endpoint_editor.dart';
|
||||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||||
import 'package:fruitshorts/pages/home/home_view.dart';
|
import 'package:fruitshorts/pages/home/home_view.dart';
|
||||||
|
|
||||||
@@ -25,6 +28,7 @@ void main() {
|
|||||||
body: HomeFailureView(
|
body: HomeFailureView(
|
||||||
error: 'SocketException: Connection refused',
|
error: 'SocketException: Connection refused',
|
||||||
onRetry: () {},
|
onRetry: () {},
|
||||||
|
onEditEndpoint: () {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -45,6 +49,10 @@ void main() {
|
|||||||
expect(error.style?.fontSize, 12);
|
expect(error.style?.fontSize, 12);
|
||||||
expect(error.style?.color, AppColors.subtle);
|
expect(error.style?.color, AppColors.subtle);
|
||||||
expect(error.maxLines, 2);
|
expect(error.maxLines, 2);
|
||||||
|
expect(
|
||||||
|
find.byKey(const Key('catalog-debug-endpoint-edit')),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should_probe_before_reloading_catalog', () async {
|
test('should_probe_before_reloading_catalog', () async {
|
||||||
@@ -57,4 +65,106 @@ void main() {
|
|||||||
|
|
||||||
expect(steps, ['probe', 'load']);
|
expect(steps, ['probe', 'load']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('should_save_probe_and_reload_after_valid_endpoint', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
AppConfig.applyRuntimeBaseUrl('http://127.0.0.1:8081');
|
||||||
|
final steps = <String>[];
|
||||||
|
await _pumpEndpointEditor(
|
||||||
|
tester,
|
||||||
|
save: (value) async {
|
||||||
|
steps.add('save:$value');
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
probe: () async => steps.add('probe'),
|
||||||
|
reload: () async => steps.add('load'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<TextField>(
|
||||||
|
find.byKey(const Key('catalog-debug-endpoint-input')),
|
||||||
|
)
|
||||||
|
.controller
|
||||||
|
?.text,
|
||||||
|
'http://127.0.0.1:8081',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
find.byKey(const Key('catalog-debug-endpoint-hint')),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
await tester.enterText(
|
||||||
|
find.byKey(const Key('catalog-debug-endpoint-input')),
|
||||||
|
'http://10.0.0.8:8081///',
|
||||||
|
);
|
||||||
|
await tester.tap(find.byKey(const Key('catalog-debug-endpoint-save')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(steps, ['save:http://10.0.0.8:8081', 'probe', 'load']);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('should_reject_invalid_endpoint_without_saving', (tester) async {
|
||||||
|
var saves = 0;
|
||||||
|
var probes = 0;
|
||||||
|
var loads = 0;
|
||||||
|
await _pumpEndpointEditor(
|
||||||
|
tester,
|
||||||
|
save: (_) async {
|
||||||
|
saves++;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
probe: () async => probes++,
|
||||||
|
reload: () async => loads++,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.enterText(
|
||||||
|
find.byKey(const Key('catalog-debug-endpoint-input')),
|
||||||
|
'not a valid url',
|
||||||
|
);
|
||||||
|
await tester.tap(find.byKey(const Key('catalog-debug-endpoint-save')));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.byKey(const Key('catalog-debug-endpoint-error')),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
find.byKey(const Key('catalog-debug-endpoint-input')),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
expect(saves, 0);
|
||||||
|
expect(probes, 0);
|
||||||
|
expect(loads, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pumpEndpointEditor(
|
||||||
|
WidgetTester tester, {
|
||||||
|
required Future<bool> Function(String value) save,
|
||||||
|
required Future<void> Function() probe,
|
||||||
|
required Future<void> Function() reload,
|
||||||
|
}) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Builder(
|
||||||
|
builder: (context) => Scaffold(
|
||||||
|
body: TextButton(
|
||||||
|
key: const Key('open-endpoint-editor'),
|
||||||
|
onPressed: () => unawaited(
|
||||||
|
showCatalogEndpointEditor(
|
||||||
|
context,
|
||||||
|
save: save,
|
||||||
|
probe: probe,
|
||||||
|
reload: reload,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('open'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.tap(find.byKey(const Key('open-endpoint-editor')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user