chore(studio): RadioGroup migration + second integration test
Two clean-ups: - `fai_system_ai_editor.dart` uses the new `RadioGroup<T>` ancestor pattern Material 3 introduced after Flutter 3.32. Same UX (three radios for off/redacted/full), no deprecation warnings, `flutter analyze` is now zero-issue for the whole project. - New `install_install_planned_test.dart` asserts that installing a `planned` seed entry surfaces a clear, human-readable error class (no panic, no silent success). Locks in the failed-precondition path Studio's friendly-error mapper depends on. Integration test suite is now 3 scenarios: - capabilities_test.dart: system.approval+kind tag - install_install_planned_test.dart: planned-install error All run against a fresh `fai serve` subprocess via HubFixture, ~2 s total. Signed-off-by: flemming-it <sf@flemming.it> Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
69b54629d3
commit
21c2a0f411
2 changed files with 94 additions and 45 deletions
|
|
@ -553,7 +553,17 @@ class _PrivacyModeChips extends StatelessWidget {
|
||||||
l.systemAiPrivacyFullDesc,
|
l.systemAiPrivacyFullDesc,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
return Column(
|
// Material 3 deprecated per-Radio `groupValue` / `onChanged`
|
||||||
|
// after Flutter 3.32. The modern pattern wraps the radios in
|
||||||
|
// a RadioGroup<T> ancestor that owns the selection + change
|
||||||
|
// callback; the individual Radio widgets only declare their
|
||||||
|
// value. Same UX, no deprecation warnings.
|
||||||
|
return RadioGroup<String>(
|
||||||
|
groupValue: value,
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v != null) onChanged(v);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -576,11 +586,7 @@ class _PrivacyModeChips extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Radio<String>(
|
Radio<String>(value: wire),
|
||||||
value: wire,
|
|
||||||
groupValue: value,
|
|
||||||
onChanged: (v) => onChanged(v ?? wire),
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -600,6 +606,7 @@ class _PrivacyModeChips extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
42
test/integration/install_install_planned_test.dart
Normal file
42
test/integration/install_install_planned_test.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Integration test: install_module against a `planned` seed
|
||||||
|
// entry must fail with a clear, human-readable error class —
|
||||||
|
// not a panic, not a silent success.
|
||||||
|
//
|
||||||
|
// Why this matters: planned modules are listed in the Store
|
||||||
|
// but have no wasm_url. The old behaviour was a server-side
|
||||||
|
// panic on resolve_install_source(); the new contract emits a
|
||||||
|
// FAILED_PRECONDITION with "no installable version" so
|
||||||
|
// Studio's friendly-error mapper can show a usable message.
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'hub_fixture.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('install_module on a planned entry surfaces a clear error',
|
||||||
|
() async {
|
||||||
|
final fixture = await HubFixture.start();
|
||||||
|
if (fixture == null) return;
|
||||||
|
try {
|
||||||
|
// web.scrape is `planned` in the seed and has no
|
||||||
|
// wasm_url. The hub resolves the name through the store
|
||||||
|
// index, finds the entry, then fails at the
|
||||||
|
// "no installable version" check.
|
||||||
|
try {
|
||||||
|
await fixture.client.installModule(source: 'web.scrape');
|
||||||
|
fail('install_module should not succeed for a planned entry');
|
||||||
|
} catch (e) {
|
||||||
|
final msg = e.toString();
|
||||||
|
expect(
|
||||||
|
msg.contains('no installable version') ||
|
||||||
|
msg.contains('not yet published'),
|
||||||
|
isTrue,
|
||||||
|
reason:
|
||||||
|
'Expected a clear planned-status error, got: $msg',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await fixture.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue