feat(settings): Appearance category + custom-control escape hatch (T-452)
Add the one bespoke control the schema engine defers to. New SettingsControlContribution routes a WidgetBuilder into a kernel SettingsControlRegistry under a customId; a SettingsFieldKind.custom field names that id, and the renderer draws the registered widget full-width (label on top, no scope tag — the control owns its own apply + scope). The theme-picker extension uses it: an Appearance category whose theme field is custom, backed by AppearanceThemeControl — base-theme chips + a high-contrast toggle that apply live through ThemeController (persisted by theme_persistence). Reuses the shared theme_families helpers. Tests: control registry (register/dup/unregister), the renderer's custom-field path, and the Appearance contribution + live theme apply. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -149,6 +149,25 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('custom field (T-452)', () {
|
||||
testWidgets('renders the control registered for its customId, full-width with its label', (tester) async {
|
||||
f.services.settingsControlRegistry.register('probe', (_) => const ClideText('PROBE-CONTROL'));
|
||||
const cat = SettingsCategory(
|
||||
id: 'c',
|
||||
title: 'C',
|
||||
sections: [
|
||||
SettingsSection(
|
||||
label: 'S',
|
||||
fields: [SettingsField(key: 'app.c.x', kind: SettingsFieldKind.custom, label: 'Custom', customId: 'probe')],
|
||||
),
|
||||
],
|
||||
);
|
||||
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: cat))));
|
||||
expect(find.text('PROBE-CONTROL'), findsOneWidget);
|
||||
expect(find.text('Custom'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
group('scope tag (T-449)', () {
|
||||
testWidgets('an unset field shows the Default scope tag', (tester) async {
|
||||
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: _category))));
|
||||
|
||||
@@ -187,5 +187,21 @@ void main() {
|
||||
expect(resp.ok, isTrue);
|
||||
expect(resp.data['selected'], 'forest');
|
||||
});
|
||||
|
||||
test('contributes an Appearance category + theme.picker control (T-452)', () {
|
||||
final ext = ThemePickerExtension();
|
||||
final cat = ext.contributions.whereType<SettingsCategoryContribution>().firstWhere((c) => c.id == 'appearance').category;
|
||||
expect(cat.title, 'Appearance');
|
||||
expect(ext.contributions.whereType<SettingsControlContribution>().any((c) => c.customId == 'theme.picker'), isTrue);
|
||||
});
|
||||
|
||||
testWidgets('AppearanceThemeControl lists base themes and applies a pick (T-452)', (tester) async {
|
||||
await tester.pumpWidget(harness(f, const SizedBox(width: 420, child: AppearanceThemeControl())));
|
||||
expect(find.text('summer-night'), findsOneWidget);
|
||||
expect(find.text('forest'), findsOneWidget);
|
||||
await tester.tap(find.text('forest'));
|
||||
await tester.pump();
|
||||
expect(f.services.theme.currentName, 'forest');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('SettingsControlRegistry', () {
|
||||
Widget builder(BuildContext _) => const SizedBox.shrink();
|
||||
|
||||
test('register exposes a builder by id', () {
|
||||
final r = SettingsControlRegistry()..register('theme.picker', builder);
|
||||
expect(r.builderFor('theme.picker'), isNotNull);
|
||||
expect(r.builderFor('missing'), isNull);
|
||||
});
|
||||
|
||||
test('duplicate id throws', () {
|
||||
final r = SettingsControlRegistry()..register('dup', builder);
|
||||
expect(() => r.register('dup', builder), throwsStateError);
|
||||
});
|
||||
|
||||
test('unregister removes the builder', () {
|
||||
final r = SettingsControlRegistry()..register('x', builder);
|
||||
r.unregister('x');
|
||||
expect(r.builderFor('x'), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user