feat(studio): HubService.invokePluginTheme + integration test
Wraps the new InvokePluginTheme RPC and ships an end-to-end test that loads studio-theme-solarized into a fresh HubFixture and asserts the light/dark schemes round-trip. Signed-off-by: flemming-it <sf@flemming.it> Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
efa9871a75
commit
a5bcde6446
2 changed files with 112 additions and 0 deletions
|
|
@ -396,6 +396,30 @@ class HubService {
|
|||
return (state: state, text: r.text, sourcePath: r.sourcePath);
|
||||
}
|
||||
|
||||
/// Invoke a Studio theme plugin's `theme` hook and unwrap
|
||||
/// the result into Flutter-friendly `ColorScheme` slots.
|
||||
///
|
||||
/// Returns a record:
|
||||
/// - `brightness`: echo of the input.
|
||||
/// - `tokens`: 14 32-bit ARGB values in Material 3
|
||||
/// order. Caller passes them to
|
||||
/// `ColorScheme(...)` after deciding the
|
||||
/// `brightness` enum.
|
||||
///
|
||||
/// Throws on hub-side errors (plugin not installed, plugin
|
||||
/// declined the brightness, etc.) — caller routes the
|
||||
/// exception through `friendlyError` like every other RPC.
|
||||
Future<({String brightness, List<int> tokens})> invokePluginTheme({
|
||||
required String capability,
|
||||
required String brightness,
|
||||
}) async {
|
||||
final r = await _client.invokePluginTheme(
|
||||
capability: capability,
|
||||
brightness: brightness,
|
||||
);
|
||||
return (brightness: r.brightness, tokens: r.tokens.toList());
|
||||
}
|
||||
|
||||
/// Snapshot of every configured MCP server.
|
||||
Future<List<McpClientInfo>> listMcpClients() async {
|
||||
final r = await _client.listMcpClients();
|
||||
|
|
|
|||
88
test/integration/plugin_theme_test.dart
Normal file
88
test/integration/plugin_theme_test.dart
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// End-to-end integration test for the Studio-plugin theme
|
||||
// hook: spins a real `fai serve`, installs the in-tree
|
||||
// `studio-theme-solarized` plugin via direct module-dir
|
||||
// staging (mirror-install would also work but takes longer
|
||||
// and pulls a network egress), then calls `invokePluginTheme`
|
||||
// and asserts the round-trip.
|
||||
//
|
||||
// Run manually with:
|
||||
//
|
||||
// flutter test test/integration/plugin_theme_test.dart
|
||||
//
|
||||
// Pre-reqs (handled by HubFixture's skip-when-missing path):
|
||||
// - fai binary on PATH or at ../fai_platform/target/release/fai
|
||||
// - studio-theme-solarized plugin built to module.wasm in its
|
||||
// fai_modules dir.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'hub_fixture.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'invokePluginTheme returns Solarized Light + Dark schemes',
|
||||
() async {
|
||||
final fixture = await HubFixture.start();
|
||||
if (fixture == null) return;
|
||||
try {
|
||||
// Stage the plugin into the fixture's modules dir
|
||||
// ourselves. Going through `installModule` would
|
||||
// require the plugin to be on the mirror, which would
|
||||
// add network + bundle-fetch latency that doesn't
|
||||
// belong in an in-process round-trip test.
|
||||
final pluginSrc = Directory(
|
||||
'../fai_modules/studio-theme-solarized',
|
||||
);
|
||||
if (!await pluginSrc.exists()) {
|
||||
markTestSkipped('studio-theme-solarized source not available');
|
||||
return;
|
||||
}
|
||||
final wasm = File('${pluginSrc.path}/module.wasm');
|
||||
if (!await wasm.exists()) {
|
||||
markTestSkipped(
|
||||
'studio-theme-solarized has no built module.wasm — '
|
||||
'run `cargo build --release --target wasm32-wasip2` first',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final dest = Directory(
|
||||
'${fixture.tempDir.path}/modules/studio-theme-solarized',
|
||||
);
|
||||
await dest.create(recursive: true);
|
||||
await File('${pluginSrc.path}/module.yaml')
|
||||
.copy('${dest.path}/module.yaml');
|
||||
await wasm.copy('${dest.path}/module.wasm');
|
||||
|
||||
// Call the hook for both brightnesses.
|
||||
final light = await fixture.client.invokePluginTheme(
|
||||
capability: 'studio.theme.solarized',
|
||||
brightness: 'light',
|
||||
);
|
||||
expect(light.brightness, 'light');
|
||||
expect(light.tokens.length, 14);
|
||||
// Solarized blue is the pinned primary in both schemes.
|
||||
expect(light.tokens[0], 0xFF268BD2);
|
||||
|
||||
final dark = await fixture.client.invokePluginTheme(
|
||||
capability: 'studio.theme.solarized',
|
||||
brightness: 'dark',
|
||||
);
|
||||
expect(dark.brightness, 'dark');
|
||||
expect(dark.tokens.length, 14);
|
||||
expect(dark.tokens[0], 0xFF268BD2);
|
||||
|
||||
// Different surface tokens between Light and Dark —
|
||||
// regression guard against a plugin that returns the
|
||||
// same scheme for both.
|
||||
expect(light.tokens[8], isNot(equals(dark.tokens[8])));
|
||||
} finally {
|
||||
await fixture.dispose();
|
||||
}
|
||||
},
|
||||
// Component-Model instantiation can take a few seconds on
|
||||
// first run as wasmtime caches the compiled artefact.
|
||||
timeout: const Timeout(Duration(minutes: 1)),
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue