Test an applet
Each SDK can run your applet without a panel: you feed it the panel's messages and check what it draws and asks for. just test runs the tests of a generated project. Run just dev as well: only the panel checks which element may hold which.
Check what the applet draws
tsx
import assert from "node:assert/strict";
import { test } from "node:test";
import type { ReactNode } from "react";
import { Button, Indicator, Popover, usePopover } from "glimpse-sdk";
import { render } from "glimpse-sdk/testing";
function Todo() {
const popover = usePopover();
return (
<>
<Indicator icon="view-list-symbolic" />
<Popover title="Todo">
<Button text="Close" onClick={popover.close} />
</Popover>
</>
);
}
test("closes the popover when the button is clicked", async () => {
const app = await render((<Todo />) as ReactNode);
try {
const tree = app.tree() as {
children: Array<{ type: string; children: Array<{
id: number; type: string; props: Record<string, unknown>;
}> }>;
};
const popover = tree.children.find((node) => node.type === "popover");
const button = popover?.children.find((node) => node.type === "button");
assert.ok(button);
assert.equal(button.props.text, "Close");
await app.click(button.id);
assert.deepEqual(app.requests(), [{ t: "close-popover" }]);
} finally {
await app.close();
}
});python
from glimpse_sdk import Session
from applet import App
def test_build_shows_the_indicator() -> None:
app = App()
session = Session(app, lambda line: None)
widgets = app.build(session.ctx)
assert widgets[0].kind == "indicator"
assert widgets[0].props["text"] == "Glimpse Todo"go
// main_test.go
package main
import (
"testing"
sdk "github.com/alex-oleshkevich/glimpse/sdk/sdk-go/sdk"
)
func TestMounts(t *testing.T) {
var lines []string
session := sdk.NewSession(&app{}, func(line string) { lines = append(lines, line) })
session.Handle(map[string]any{"t": "hello", "v": float64(1), "name": "test",
"state": t.TempDir(), "options": map[string]any{}})
session.Flush()
if len(lines) == 0 {
t.Fatal("no commit")
}
}rust
// at the end of src/main.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mounts() {
let (tx, rx) = std::sync::mpsc::channel();
let mut session = Session::new(App, tx);
session.handle(&json!({"t": "hello", "v": 1, "name": "test", "state": "", "options": {}}));
session.flush().expect("a valid tree");
assert!(rx.try_recv().is_ok());
}
}Save a TypeScript test as <name>.test.tsx in the project folder. just test bundles each one into build/tests/ and runs it with node --test. The Python test is the one glimpsectl applet new generates. In Go and Rust, each line the session writes is a protocol message; a commit holds the operations that build the tree, described in Applet protocol.
Drive a TypeScript applet
ts
function render(
element: ReactNode,
host?: {
options?: Record<string, unknown>;
placement?: Record<string, unknown>;
state?: string;
},
): Promise<Rendered>;
interface Rendered {
tree(): unknown; // id, type, props, children; narrow it first
requests(): Array<Record<string, unknown>>; // e.g. { t: "close-popover" }
click(id: number): Promise<void>; // clicks a Button or activates an ActionRow
event(id: number, name: string, args?: unknown[]): Promise<void>; // e.g. "onToggle", [true]
setOptions(options: Record<string, unknown>): Promise<void>;
setPopoverOpen(open: boolean): Promise<void>; // what usePopover().open sees
close(): Promise<void>; // unmounts; await it before the next render
}Without state, the test gets a temporary directory that close() removes.
The in-memory host checks the size limits but not nesting or text cleaning.