test(settings): cover the schema-engine edit/file/scope/search paths

Exercise the previously-untested branches of the settings category renderer:
number-field commit/clamp/unparseable-revert, text-field commit, the file
control's command button, the select raw-value fallback, scope move-to-app,
field help text, and the cross-category search empty state. Restores the
coverage floor after the D-101 facade migration shifted the ratio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 22:27:59 +02:00
co-authored by Claude Opus 4.8
parent a5d6559d24
commit 18c8e702fb
@@ -192,6 +192,145 @@ void main() {
await tester.pump();
expect(f.services.settings.effectiveLayer('app.demo.flag'), isNull);
});
testWidgets('moving an unset value to All clide writes it at app scope', (tester) async {
// _other's key (app.other.x) is written by no other test → pristine here.
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: _other))));
await tester.tap(find.bySemanticsLabel('OtherFlag scope: Default'));
await tester.pump();
await tester.tap(find.text('All clide'));
await tester.pump();
expect(f.services.settings.effectiveLayer('app.other.x'), SettingsScope.app);
});
});
group('edit controls (T-448)', () {
const numCat = SettingsCategory(
id: 'n',
title: 'N',
sections: [
SettingsSection(
label: 'S',
fields: [SettingsField(key: 'app.n.size', kind: SettingsFieldKind.number, label: 'Size', defaultValue: 4, min: 1, max: 8)],
),
],
);
const textCat = SettingsCategory(
id: 't',
title: 'T',
sections: [
SettingsSection(
label: 'S',
fields: [SettingsField(key: 'app.t.name', kind: SettingsFieldKind.text, label: 'Name', defaultValue: '')],
),
],
);
testWidgets('a number field commits a valid value', (tester) async {
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: numCat))));
await tester.enterText(find.byType(EditableText), '6');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pump();
expect(f.services.settings.get<num>('app.n.size'), 6);
});
testWidgets('a number field clamps above max and below min', (tester) async {
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: numCat))));
await tester.enterText(find.byType(EditableText), '99');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pump();
expect(f.services.settings.get<num>('app.n.size'), 8);
await tester.enterText(find.byType(EditableText), '0');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pump();
expect(f.services.settings.get<num>('app.n.size'), 1);
});
testWidgets('a number field reverts unparseable input without writing', (tester) async {
// Dedicated key — the store is shared across tests in this file, so a key
// the commit/clamp tests touch would already be set.
const revCat = SettingsCategory(
id: 'nr',
title: 'NR',
sections: [
SettingsSection(
label: 'S',
fields: [SettingsField(key: 'app.nr.size', kind: SettingsFieldKind.number, label: 'Size', defaultValue: 4, min: 1, max: 8)],
),
],
);
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: revCat))));
await tester.enterText(find.byType(EditableText), 'abc');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pump();
// Unparseable input is reverted to the field's value, not committed; the
// string 'abc' never reaches the store.
expect(find.text('4'), findsOneWidget);
expect(f.services.settings.get<Object>('app.nr.size'), isNot('abc'));
});
testWidgets('a text field commits the trimmed value', (tester) async {
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: textCat))));
await tester.enterText(find.byType(EditableText), ' hello ');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pump();
expect(f.services.settings.get<String>('app.t.name'), 'hello');
});
testWidgets('a select shows the raw stored value when it is not among the options', (tester) async {
await tester.runAsync(() => f.services.settings.set('app.demo.level', 'trace'));
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: _category))));
expect(find.text('trace'), findsOneWidget);
});
testWidgets('a file field renders a button that runs its command', (tester) async {
var ran = false;
f.services.commands.register(
CommandContribution(
id: 'test.openfile',
command: 'test.openfile',
run: (_) async {
ran = true;
return IpcResponse.ok(id: '', data: const {});
},
),
);
const cat = SettingsCategory(
id: 'fc',
title: 'FC',
sections: [
SettingsSection(
label: 'S',
fields: [SettingsField(key: 'app.fc.cfg', kind: SettingsFieldKind.file, label: 'Edit config', fileCommand: 'test.openfile')],
),
],
);
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: cat))));
await tester.tap(find.widgetWithText(ClideButton, 'Edit config'));
await tester.pump();
expect(ran, isTrue);
});
testWidgets('a field renders its help text', (tester) async {
const cat = SettingsCategory(
id: 'h',
title: 'H',
sections: [
SettingsSection(
label: 'S',
fields: [SettingsField(key: 'app.h.flag', kind: SettingsFieldKind.toggle, label: 'Flag', help: 'Toggle the thing', defaultValue: false)],
),
],
);
await tester.pumpWidget(harness(f, _bounded(const SettingsCategoryView(category: cat))));
expect(find.text('Toggle the thing'), findsOneWidget);
});
testWidgets('search results show an empty state when nothing matches', (tester) async {
f.services.settingsRegistry.register(_category);
await tester.pumpWidget(harness(f, _bounded(const SettingsSearchResults(query: 'zzznomatch'))));
expect(find.text('No settings match your search.'), findsOneWidget);
});
});
group('SettingsModal with a registered category', () {