Testing with the fake backend
Run the whole protocol in-process for widget tests, demos and CI, including failures you cannot reproduce against a real server.
package:kletso_core/fake.dart provides KletsoFakeBackend, which implements both the API and the transport:
final backend = KletsoFakeBackend(scenario: const KletsoFakeScenario.demo());
final client = await Kletso.init(
KletsoConfig(publishableKey: 'kl_pub_demo', agentId: KletsoFakeBackend.agentId),
api: backend, transport: backend,
);
What it plays
A scripted assistant that reacts to keywords: flight (flight card with a select action), hotel, products (custom acme.productCard cards), sales (chart), where is my courier (map, video, audio), my trip plan (tabs, steps, accordion), cancel my order (tool confirmation), callback (form), order, human (handoff), error, long, and open trail runner (an app.command). Trigger rules for cart_abandoned, screen('checkout'), geofence_entered, payment_failed, order_shipped produce prepared turns and notifications. backend.notify(userId, n) pushes a live notification; backend.sendPush(userId, n) returns a push payload for handlePushPayload.
Scenario knobs
const KletsoFakeScenario() is instant and reliable; KletsoFakeScenario.demo(...) is lifelike. Fields: apiLatency, thinkingDelay, deltaDelay, toolDelay, failHandshakes, dropSocketAfterEvents (+ dropSocketCloseCode), duplicateEveryNth, expireTokenAfterEvents, rejectToken, rateLimitEveryNthMessage, seedConversationLog, greeting, sampleMedia.
Inspect what the app sent with backend.frames, backend.logOf(conversationId), backend.contextOf(userId), backend.pushTokens. Force conditions with backend.dropAllSockets(), backend.expireTokens(), backend.emit(type, data).
Widget tests
testWidgets('renders product cards', (tester) async {
final backend = KletsoFakeBackend(scenario: const KletsoFakeScenario());
final client = KletsoClient(config, api: backend, transport: backend);
await client.identifyAnonymous();
await tester.pumpWidget(MaterialApp(home: KletsoChat(client: client)));
await client.send(KletsoOutbound.text('show me products'));
await tester.pump(const Duration(milliseconds: 500));
expect(find.byType(KletsoSurfaceView), findsOneWidget);
});
The fake answers on real timers, so pump with durations rather than pumpAndSettle. Surfaces can also be rendered directly from fixtures: KletsoSurface.parse(KletsoFixtures.ui('product_cards')).
Validating surfaces
KletsoSurface.parse(json) returns KletsoSurfaceOk or KletsoSurfaceRejected with a list of issues, never throws. Set debugValidateUi: true in debug builds to log issues for every rendered surface.