Align desktop ACP card actions
This commit is contained in:
parent
94b6e38738
commit
b2ddbd62c0
@ -68,6 +68,14 @@ class SettingsPageStateInternal extends State<SettingsPage> {
|
||||
late final TextEditingController vaultTokenControllerInternal;
|
||||
late final TextEditingController ollamaApiKeyControllerInternal;
|
||||
late final TextEditingController runtimeLogFilterControllerInternal;
|
||||
late final Map<String, TextEditingController>
|
||||
externalAcpLabelControllersInternal;
|
||||
late final Map<String, TextEditingController>
|
||||
externalAcpEndpointControllersInternal;
|
||||
late final Map<String, String> externalAcpLabelSyncedValuesInternal;
|
||||
late final Map<String, String> externalAcpEndpointSyncedValuesInternal;
|
||||
late final Map<String, String> externalAcpMessageByProviderInternal;
|
||||
late final Set<String> externalAcpTestingProvidersInternal;
|
||||
bool gatewayTestingInternal = false;
|
||||
String gatewayTestStateInternal = 'idle';
|
||||
String gatewayTestMessageInternal = '';
|
||||
@ -138,6 +146,12 @@ class SettingsPageStateInternal extends State<SettingsPage> {
|
||||
vaultTokenControllerInternal = TextEditingController();
|
||||
ollamaApiKeyControllerInternal = TextEditingController();
|
||||
runtimeLogFilterControllerInternal = TextEditingController();
|
||||
externalAcpLabelControllersInternal = <String, TextEditingController>{};
|
||||
externalAcpEndpointControllersInternal = <String, TextEditingController>{};
|
||||
externalAcpLabelSyncedValuesInternal = <String, String>{};
|
||||
externalAcpEndpointSyncedValuesInternal = <String, String>{};
|
||||
externalAcpMessageByProviderInternal = <String, String>{};
|
||||
externalAcpTestingProvidersInternal = <String>{};
|
||||
}
|
||||
|
||||
void setStateInternal(VoidCallback fn) => setState(fn);
|
||||
@ -201,6 +215,12 @@ class SettingsPageStateInternal extends State<SettingsPage> {
|
||||
vaultTokenControllerInternal.dispose();
|
||||
ollamaApiKeyControllerInternal.dispose();
|
||||
runtimeLogFilterControllerInternal.dispose();
|
||||
for (final controller in externalAcpLabelControllersInternal.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
for (final controller in externalAcpEndpointControllersInternal.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -163,10 +163,7 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
GatewayIntegrationSubTabInternal.skills => <Widget>[
|
||||
buildCollapsibleGatewaySectionInternal(
|
||||
context: context,
|
||||
title: appText(
|
||||
'SKILLS 目录授权',
|
||||
'SKILLS Directory Authorization',
|
||||
),
|
||||
title: appText('SKILLS 目录授权', 'SKILLS Directory Authorization'),
|
||||
expanded: skillsDirectoryAuthorizationExpandedInternal,
|
||||
onChanged: (value) => setStateInternal(() {
|
||||
skillsDirectoryAuthorizationExpandedInternal = value;
|
||||
@ -248,10 +245,7 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
const SizedBox(height: 16),
|
||||
buildCollapsibleGatewaySectionInternal(
|
||||
context: context,
|
||||
title: appText(
|
||||
'SKILLS 目录授权',
|
||||
'SKILLS Directory Authorization',
|
||||
),
|
||||
title: appText('SKILLS 目录授权', 'SKILLS Directory Authorization'),
|
||||
expanded: skillsDirectoryAuthorizationExpandedInternal,
|
||||
onChanged: (value) => setStateInternal(() {
|
||||
skillsDirectoryAuthorizationExpandedInternal = value;
|
||||
@ -269,6 +263,7 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
) {
|
||||
syncExternalAcpDraftControllersInternal(settings);
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -291,14 +286,13 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
settings,
|
||||
),
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
label: Text(
|
||||
appText('添加更多自定义配置', 'Add more custom configurations'),
|
||||
),
|
||||
label: Text(appText('添加更多自定义配置', 'Add more custom configurations')),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...settings.externalAcpEndpoints.map(
|
||||
(profile) => Padding(
|
||||
key: ValueKey('external-acp-card-${profile.providerKey}'),
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: buildExternalAcpProviderCardInternal(
|
||||
context,
|
||||
@ -319,8 +313,16 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
ExternalAcpEndpointProfile profile,
|
||||
) {
|
||||
final provider = profile.toProvider();
|
||||
final endpoint = profile.endpoint.trim();
|
||||
final configured = endpoint.isNotEmpty;
|
||||
final labelController =
|
||||
externalAcpLabelControllersInternal[profile.providerKey]!;
|
||||
final endpointController =
|
||||
externalAcpEndpointControllersInternal[profile.providerKey]!;
|
||||
final message =
|
||||
externalAcpMessageByProviderInternal[profile.providerKey] ?? '';
|
||||
final testing = externalAcpTestingProvidersInternal.contains(
|
||||
profile.providerKey,
|
||||
);
|
||||
final configured = endpointController.text.trim().isNotEmpty;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
@ -366,27 +368,21 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
EditableFieldInternal(
|
||||
label: appText('显示名称', 'Display name'),
|
||||
value: profile.label,
|
||||
onSubmitted: (value) => saveSettingsInternal(
|
||||
controller,
|
||||
settings.copyWithExternalAcpEndpointForProvider(
|
||||
provider,
|
||||
profile.copyWith(label: value),
|
||||
),
|
||||
TextField(
|
||||
key: ValueKey('external-acp-label-${profile.providerKey}'),
|
||||
controller: labelController,
|
||||
decoration: InputDecoration(
|
||||
labelText: appText('显示名称', 'Display name'),
|
||||
),
|
||||
onChanged: (_) => setStateInternal(() {}),
|
||||
),
|
||||
EditableFieldInternal(
|
||||
label: appText('ACP Server Endpoint', 'ACP Server Endpoint'),
|
||||
value: endpoint,
|
||||
onSubmitted: (value) => saveSettingsInternal(
|
||||
controller,
|
||||
settings.copyWithExternalAcpEndpointForProvider(
|
||||
provider,
|
||||
profile.copyWith(endpoint: value),
|
||||
),
|
||||
TextField(
|
||||
key: ValueKey('external-acp-endpoint-${profile.providerKey}'),
|
||||
controller: endpointController,
|
||||
decoration: InputDecoration(
|
||||
labelText: appText('ACP Server Endpoint', 'ACP Server Endpoint'),
|
||||
),
|
||||
onChanged: (_) => setStateInternal(() {}),
|
||||
),
|
||||
Text(
|
||||
appText(
|
||||
@ -395,11 +391,132 @@ extension SettingsPageGatewayMixinInternal on SettingsPageStateInternal {
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
key: ValueKey('external-acp-test-${profile.providerKey}'),
|
||||
onPressed: testing
|
||||
? null
|
||||
: () => testExternalAcpEndpointInternal(
|
||||
controller,
|
||||
profile.providerKey,
|
||||
),
|
||||
child: Text(
|
||||
testing
|
||||
? appText('测试中...', 'Testing...')
|
||||
: appText('测试连接', 'Test Connection'),
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
key: ValueKey('external-acp-apply-${profile.providerKey}'),
|
||||
onPressed: () => saveExternalAcpEndpointInternal(
|
||||
controller,
|
||||
settings,
|
||||
provider,
|
||||
profile,
|
||||
),
|
||||
child: Text(appText('保存并生效', 'Save & apply')),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (message.trim().isNotEmpty) ...[
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
message,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> saveExternalAcpEndpointInternal(
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
SingleAgentProvider provider,
|
||||
ExternalAcpEndpointProfile profile,
|
||||
) async {
|
||||
final label =
|
||||
externalAcpLabelControllersInternal[profile.providerKey]?.text ??
|
||||
profile.label;
|
||||
final endpoint =
|
||||
externalAcpEndpointControllersInternal[profile.providerKey]?.text ??
|
||||
profile.endpoint;
|
||||
final next = settings.copyWithExternalAcpEndpointForProvider(
|
||||
provider,
|
||||
profile.copyWith(label: label, endpoint: endpoint),
|
||||
);
|
||||
await saveSettingsInternal(controller, next);
|
||||
await handleTopLevelApplyInternal(controller);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setStateInternal(() {
|
||||
externalAcpMessageByProviderInternal[profile.providerKey] = appText(
|
||||
'配置已保存并生效。',
|
||||
'Configuration saved and applied.',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> testExternalAcpEndpointInternal(
|
||||
AppController controller,
|
||||
String providerKey,
|
||||
) async {
|
||||
final endpointText =
|
||||
externalAcpEndpointControllersInternal[providerKey]?.text.trim() ?? '';
|
||||
final endpoint = Uri.tryParse(endpointText);
|
||||
if (endpoint == null || endpoint.host.trim().isEmpty) {
|
||||
setStateInternal(() {
|
||||
externalAcpMessageByProviderInternal[providerKey] = appText(
|
||||
'请输入有效的 ACP Server Endpoint。',
|
||||
'Enter a valid ACP server endpoint.',
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
setStateInternal(() {
|
||||
externalAcpTestingProvidersInternal.add(providerKey);
|
||||
externalAcpMessageByProviderInternal.remove(providerKey);
|
||||
});
|
||||
try {
|
||||
final capabilities = await controller.gatewayAcpClientInternal
|
||||
.loadCapabilities(forceRefresh: true, endpointOverride: endpoint);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setStateInternal(() {
|
||||
externalAcpMessageByProviderInternal[providerKey] = appText(
|
||||
capabilities.providers.isEmpty
|
||||
? '连接成功。'
|
||||
: '连接成功,可用 Provider: ${capabilities.providers.map((item) => item.label).join(' / ')}',
|
||||
capabilities.providers.isEmpty
|
||||
? 'Connection succeeded.'
|
||||
: 'Connection succeeded. Providers: ${capabilities.providers.map((item) => item.label).join(' / ')}',
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setStateInternal(() {
|
||||
externalAcpMessageByProviderInternal[providerKey] = '$error';
|
||||
});
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setStateInternal(() {
|
||||
externalAcpTestingProvidersInternal.remove(providerKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showAddExternalAcpProviderWizardInternal(
|
||||
BuildContext context,
|
||||
AppController controller,
|
||||
|
||||
@ -307,6 +307,67 @@ XWorkmate Privacy Policy
|
||||
);
|
||||
}
|
||||
|
||||
void syncExternalAcpDraftControllersInternal(SettingsSnapshot settings) {
|
||||
final activeKeys = settings.externalAcpEndpoints
|
||||
.map((item) => item.providerKey)
|
||||
.toSet();
|
||||
for (final profile in settings.externalAcpEndpoints) {
|
||||
final key = profile.providerKey;
|
||||
final labelController = externalAcpLabelControllersInternal.putIfAbsent(
|
||||
key,
|
||||
() => TextEditingController(),
|
||||
);
|
||||
final endpointController = externalAcpEndpointControllersInternal
|
||||
.putIfAbsent(key, () => TextEditingController());
|
||||
syncDraftControllerValueInternal(
|
||||
labelController,
|
||||
profile.label,
|
||||
syncedValue: externalAcpLabelSyncedValuesInternal[key] ?? '',
|
||||
onSyncedValueChanged: (value) =>
|
||||
externalAcpLabelSyncedValuesInternal[key] = value,
|
||||
);
|
||||
syncDraftControllerValueInternal(
|
||||
endpointController,
|
||||
profile.endpoint,
|
||||
syncedValue: externalAcpEndpointSyncedValuesInternal[key] ?? '',
|
||||
onSyncedValueChanged: (value) =>
|
||||
externalAcpEndpointSyncedValuesInternal[key] = value,
|
||||
);
|
||||
}
|
||||
disposeRemovedExternalAcpDraftsInternal(
|
||||
externalAcpLabelControllersInternal,
|
||||
activeKeys,
|
||||
);
|
||||
disposeRemovedExternalAcpDraftsInternal(
|
||||
externalAcpEndpointControllersInternal,
|
||||
activeKeys,
|
||||
);
|
||||
externalAcpLabelSyncedValuesInternal.removeWhere(
|
||||
(key, _) => !activeKeys.contains(key),
|
||||
);
|
||||
externalAcpEndpointSyncedValuesInternal.removeWhere(
|
||||
(key, _) => !activeKeys.contains(key),
|
||||
);
|
||||
externalAcpMessageByProviderInternal.removeWhere(
|
||||
(key, _) => !activeKeys.contains(key),
|
||||
);
|
||||
externalAcpTestingProvidersInternal.removeWhere(
|
||||
(key) => !activeKeys.contains(key),
|
||||
);
|
||||
}
|
||||
|
||||
void disposeRemovedExternalAcpDraftsInternal(
|
||||
Map<String, TextEditingController> controllers,
|
||||
Set<String> activeKeys,
|
||||
) {
|
||||
final removedKeys = controllers.keys
|
||||
.where((key) => !activeKeys.contains(key))
|
||||
.toList(growable: false);
|
||||
for (final key in removedKeys) {
|
||||
controllers.remove(key)?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
GatewayConnectionProfile selectedGatewayProfileInternal(
|
||||
SettingsSnapshot settings,
|
||||
) {
|
||||
|
||||
@ -159,11 +159,7 @@ void main() {
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.appearance,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.appearance);
|
||||
await tester.tap(find.text('深色'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@ -221,78 +217,68 @@ void main() {
|
||||
(WidgetTester tester) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.workspace,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.workspace);
|
||||
|
||||
expect(find.text('远程项目根目录'), findsNothing);
|
||||
expect(find.text('Remote Project Root'), findsNothing);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('SettingsPage renders only the active section without internal tabs', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
testWidgets(
|
||||
'SettingsPage renders only the active section without internal tabs',
|
||||
(WidgetTester tester) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await pumpPage(
|
||||
tester,
|
||||
child: SettingsPage(
|
||||
controller: controller,
|
||||
showSectionTabs: false,
|
||||
),
|
||||
platform: TargetPlatform.macOS,
|
||||
);
|
||||
await pumpPage(
|
||||
tester,
|
||||
child: SettingsPage(controller: controller, showSectionTabs: false),
|
||||
platform: TargetPlatform.macOS,
|
||||
);
|
||||
|
||||
expect(find.byType(SectionTabs), findsNothing);
|
||||
expect(find.text('Application'), findsOneWidget);
|
||||
expect(find.text('OpenClaw Gateway'), findsNothing);
|
||||
expect(find.text('LLM 接入点'), findsNothing);
|
||||
expect(find.text('工作区路径'), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('external-acp-provider-add-button')),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
expect(find.byType(SectionTabs), findsNothing);
|
||||
expect(find.text('Application'), findsOneWidget);
|
||||
expect(find.text('OpenClaw Gateway'), findsNothing);
|
||||
expect(find.text('LLM 接入点'), findsNothing);
|
||||
expect(find.text('工作区路径'), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('external-acp-provider-add-button')),
|
||||
findsNothing,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('SettingsPage workspace edits enable the top save-and-apply flow', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
testWidgets(
|
||||
'SettingsPage workspace edits enable the top save-and-apply flow',
|
||||
(WidgetTester tester) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.workspace,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.workspace);
|
||||
|
||||
await tester.enterText(
|
||||
find.byType(TextFormField).first,
|
||||
'/tmp/xworkmate-workspace',
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.enterText(
|
||||
find.byType(TextFormField).first,
|
||||
'/tmp/xworkmate-workspace',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(controller.settingsDraft.workspacePath, '/tmp/xworkmate-workspace');
|
||||
expect(controller.hasSettingsDraftChanges, isTrue);
|
||||
expect(
|
||||
controller.settingsDraft.workspacePath,
|
||||
'/tmp/xworkmate-workspace',
|
||||
);
|
||||
expect(controller.hasSettingsDraftChanges, isTrue);
|
||||
|
||||
final applyButton = tester.widget<FilledButton>(
|
||||
find.byKey(const ValueKey('settings-global-apply-button')),
|
||||
);
|
||||
expect(applyButton.onPressed, isNotNull);
|
||||
});
|
||||
final applyButton = tester.widget<FilledButton>(
|
||||
find.byKey(const ValueKey('settings-global-apply-button')),
|
||||
);
|
||||
expect(applyButton.onPressed, isNotNull);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('SettingsPage integration tab exposes unified gateway controls', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
expect(find.text('OpenClaw Gateway'), findsWidgets);
|
||||
expect(find.text('LLM 接入点'), findsOneWidget);
|
||||
@ -348,7 +334,10 @@ void main() {
|
||||
find.byKey(const ValueKey('external-acp-provider-add-button')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byKey(const ValueKey('settings-global-apply-button')), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('settings-global-apply-button')),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('SettingsPage vault card exposes concrete K/V fields', (
|
||||
@ -356,11 +345,7 @@ void main() {
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
expect(find.text('Vault Server'), findsAtLeastNWidgets(1));
|
||||
expect(find.text('VAULT_SERVER_URL'), findsOneWidget);
|
||||
@ -377,11 +362,7 @@ void main() {
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
expect(find.text('外部 ACP Server Endpoint'), findsOneWidget);
|
||||
expect(find.textContaining('Codex'), findsWidgets);
|
||||
@ -411,11 +392,7 @@ void main() {
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
await _ensureVisible(
|
||||
tester,
|
||||
@ -451,11 +428,7 @@ void main() {
|
||||
_FakeSkillDirectoryAccessService(userHomeDirectory: '/Users/tester'),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
expect(find.text('~/.agents/skills'), findsOneWidget);
|
||||
expect(find.text('/Users/tester/.agents/skills'), findsOneWidget);
|
||||
@ -485,11 +458,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await _ensureVisible(
|
||||
tester,
|
||||
find.byKey(const ValueKey('skill-directory-batch-add-button')),
|
||||
@ -546,11 +515,7 @@ paths:
|
||||
),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await _ensureVisible(
|
||||
tester,
|
||||
find.byKey(const ValueKey('skill-directory-batch-add-button')),
|
||||
@ -590,11 +555,7 @@ paths:
|
||||
_FakeSkillDirectoryAccessService(userHomeDirectory: '/Users/tester'),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await _ensureVisible(
|
||||
tester,
|
||||
find.byKey(const ValueKey('skill-directory-batch-add-button')),
|
||||
@ -635,11 +596,7 @@ paths:
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
await tester.tap(find.byTooltip('折叠').first);
|
||||
await tester.pumpAndSettle();
|
||||
@ -667,11 +624,7 @@ paths:
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
await _ensureVisible(tester, find.text('外部 ACP Server Endpoint'));
|
||||
await tester.tap(find.text('外部 ACP Server Endpoint').first);
|
||||
@ -693,6 +646,50 @@ paths:
|
||||
expect(find.textContaining('OpenCode'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('SettingsPage external ACP card supports continuous input', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
final customProfile = buildCustomExternalAcpEndpointProfile(
|
||||
controller.settingsDraft.externalAcpEndpoints,
|
||||
label: 'Initial Name',
|
||||
endpoint: 'wss://initial.example.com/acp',
|
||||
);
|
||||
await controller.saveSettingsDraft(
|
||||
controller.settingsDraft.copyWith(
|
||||
externalAcpEndpoints: <ExternalAcpEndpointProfile>[
|
||||
...controller.settingsDraft.externalAcpEndpoints,
|
||||
customProfile,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
|
||||
final labelField = find.byKey(
|
||||
ValueKey('external-acp-label-${customProfile.providerKey}'),
|
||||
);
|
||||
final testButton = find.byKey(
|
||||
ValueKey('external-acp-test-${customProfile.providerKey}'),
|
||||
);
|
||||
final applyButton = find.byKey(
|
||||
ValueKey('external-acp-apply-${customProfile.providerKey}'),
|
||||
);
|
||||
|
||||
expect(labelField, findsOneWidget);
|
||||
expect(testButton, findsOneWidget);
|
||||
expect(applyButton, findsOneWidget);
|
||||
|
||||
await tester.enterText(labelField, 'A');
|
||||
await tester.pump();
|
||||
await tester.enterText(labelField, 'AB');
|
||||
await tester.pump();
|
||||
await tester.enterText(labelField, 'ABC');
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('ABC'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('SettingsPage shows Linux desktop integration controls', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
@ -758,11 +755,7 @@ paths:
|
||||
) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.gateway,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway);
|
||||
await tester.tap(find.byKey(const ValueKey('gateway-profile-chip-1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@ -847,11 +840,7 @@ paths:
|
||||
(WidgetTester tester) async {
|
||||
final controller = await createTestController(tester);
|
||||
|
||||
await _pumpSettingsPage(
|
||||
tester,
|
||||
controller,
|
||||
tab: SettingsTab.diagnostics,
|
||||
);
|
||||
await _pumpSettingsPage(tester, controller, tab: SettingsTab.diagnostics);
|
||||
|
||||
expect(
|
||||
find.byKey(const ValueKey('assistant-local-state-card')),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user