Emulator & testing
The emulator is a self-contained reimplementation of the parts of the Grist plugin API the SDK uses. It's available under two subpaths:
| Subpath | Purpose |
|---|---|
grist-widget-sdk/emulator | Core emulator (createGristEmulator, document validators). |
grist-widget-sdk/emulator/testing | React Testing Library integration (renderWithGrist, presets, matchers). |
createGristEmulator(options)
Builds an emulator instance and installs it as window.grist.
import { createGristEmulator } from "grist-widget-sdk/emulator"
const emulator = createGristEmulator({
document: presets.todoList(),
transport: { kind: "inline" }, // or { kind: "iframe", iframe: HTMLIFrameElement }
install: true, // default: also sets window.grist
})
// emulator.mutate, emulator.cursor, emulator.options, emulator.theme, emulator.events, ...
emulator.dispose()Document
document must be a complete GristReplicaDocument (see /design/replica-document). Validate untrusted input first:
import { parseDocument } from "grist-widget-sdk/emulator"
const doc = parseDocument(json)Transports
| Transport | Use |
|---|---|
inline (default) | The emulator and your code run in the same realm. Fast, used in tests. |
iframe | The emulator runs in a parent window and proxies messages to an iframe. Used for storybook-style previews. |
mountGristEmulator(element, options)
Higher-level helper for browser hosts:
const handle = mountGristEmulator(rootEl, {
document: presets.contacts(),
iframe: { src: "/widget.html" },
})
// handle.dispose()This wires up an iframe and installs the emulator as a parent on it. Use it in playground apps to preview production widgets.
Mutators
The emulator exposes imperative methods (also used by the testing helpers):
emulator.mutate.addRow(tableId, fields) // returns new row id
emulator.mutate.updateRow(tableId, rowId, fields)
emulator.mutate.removeRow(tableId, rowId)
emulator.mutate.bulkAddRows(tableId, columnar)
emulator.cursor.select(tableId, rowId)
emulator.cursor.selectNewRow()
emulator.cursor.clear()
emulator.options.set(value)
emulator.options.patch(patch)
emulator.options.clear()
emulator.theme.set("light" | "dark")
emulator.docInfo.set({ name: "My Doc" })Theme updates use the same host postMessage shape as production Grist (msg.theme with appearance, delivered on grist.on("message")). Optional initialTheme on createGristEmulator replays once on widget ready.
All mutations are reflected through the normal event streams (onRecord, onRecords, onOptions, theme messages) — the SDK takes its real path.
Inspecting events and actions
const events = emulator.events.snapshot()
// [{ kind: "ready" }, { kind: "record", record: { ... } }, { kind: "records", ... }, ...]
const actions = emulator.actions.snapshot()
// All applyUserActions tuples seen so far.These are also accessible via eventsOf(emulator) and actionsOf(emulator) re-exports from the testing entry.
Testing entry
import {
renderWithGrist,
presets,
actionsOf,
eventsOf,
waitForEvent,
} from "grist-widget-sdk/emulator/testing"renderWithGrist(ui, options?)
A thin wrapper over @testing-library/react's render. Adds:
type RenderWithGristOptions = RenderOptions & {
emulator?: CreateGristEmulatorOptions
existing?: GristEmulator
}If neither emulator nor existing is given, a blank emulator is created and torn down on unmount.
Returns a RenderResult with two extra fields:
type RenderWithGristResult = RenderResult & {
emulator: GristEmulator
dispose: () => void
}presets
presets.blank()
presets.todoList()
presets.contacts()Returns ready-to-pass GristReplicaDocument objects. Useful for quick starts; build your own for thorough tests.
actionsOf(emulator) / eventsOf(emulator)
Convenience snapshots — equivalent to emulator.actions.snapshot() and emulator.events.snapshot().
waitForEvent(emulator, kind, opts?)
Waits for the next matching bus event (ignores events that were already in history when the call started).
await waitForEvent(emulator, "options", { timeoutMs: 2000 })
await waitForEvent(emulator, (e) => e.type === "user-action" && e.action[0] === "AddRecord")Kinds (map to emulator bus events):
| Kind | Bus event |
|---|---|
ready | ready |
record | cursor-change (drives onRecord) |
records | data-change (drives onRecords) |
options | options-change |
theme | theme-change |
cursor | cursor-change |
Rejects with waitForEvent timed out after …ms (kind: …) and a short list of recent bus event types. Useful for async write flows.