Skip to content
Open
138 changes: 138 additions & 0 deletions apps/smoke/fixtures/vx-runtime-browser.voyd
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::enums::{ enum }
use std::msgpack::self as msgpack
use std::string::type::String
use std::vx::all

obj Model {
api status: String,
api key: String,
api code: String,
api ctrl: bool
}

enum Msg
KeyDown { key: String, code: String, ctrl: bool }
ClipboardRead { value: String }
LocationChanged { href: String }
WindowEvent { event: String }
Frame { timestamp: f64 }
Media { matches: bool }
Storage { key: String }
Broadcast { value: String }
Changed

pub fn app() -> Program<Model, Msg>
program({ init, step, view, subscriptions })

fn init() -> Program<Model, Msg>
next<Model, Msg>(
model: Model { status: "ready", key: "none", code: "none", ctrl: false },
cmd: Cmd<Msg>::copy_to_clipboard("Copied from Voyd")
)

fn step(model: Model, msg: Msg) -> Program<Model, Msg>
match(msg)
Msg::KeyDown { key, code, ctrl }:
next<Model, Msg>(Model { status: "key", key: key, code: code, ctrl: ctrl })
else:
next<Model, Msg>(model)

fn view(model: Model) -> Html<Msg>
<div>
<p class="status">{model.status}</p>
<p class="key">{model.key}</p>
<p class="code">{model.code}</p>
<p class="ctrl">{ctrl_label(model.ctrl)}</p>
</div>

fn subscriptions(model: Model) -> Sub<Msg>
keyboard_on_key_down(
key: "s",
handler: (event: KeyboardEvent) -> Msg =>
Msg::KeyDown { key: event.key, code: event.code, ctrl: event.ctrl_key }
)

pub fn standard_commands() -> Cmd<Msg>
let editor = Ref<DomElement> { id: "editor" }
Cmd::batch([
Cmd<Msg>::copy_to_clipboard("Copied from Voyd"),
Cmd<Msg>::read_clipboard((value: String) -> Msg => Msg::ClipboardRead { value: value }),
Cmd<Msg>::set_document_title("VX"),
Cmd<Msg>::push_url("/next"),
Cmd<Msg>::replace_url("/next?replace=1"),
Cmd<Msg>::set_hash("section"),
Cmd<Msg>::navigate_back(),
Cmd<Msg>::navigate_forward(),
Cmd<Msg>::open_url("/external"),
Cmd<Msg>::open_url(url: "/external", target: "_self"),
Cmd<Msg>::scroll_window_to(x: 0.0, y: 10.0),
Cmd<Msg>::scroll_window_by(x: 1.0, y: 2.0),
Cmd<Msg>::local_storage_set(key: "draft", value: "yes"),
Cmd<Msg>::local_storage_remove("draft"),
Cmd<Msg>::local_storage_clear(),
Cmd<Msg>::session_storage_set(key: "draft", value: "yes"),
Cmd<Msg>::session_storage_remove("draft"),
Cmd<Msg>::session_storage_clear(),
Cmd<Msg>::focus(editor),
Cmd<Msg>::scroll_into_view(editor),
Cmd<Msg>::select_text(editor)
])

pub fn standard_subscriptions() -> Sub<Msg>
Sub::batch([
keyboard_on_key_down(key: "s", value: Msg::Changed {}),
keyboard_on_key_up(
key: "s",
handler: (event: KeyboardEvent) -> Msg =>
Msg::KeyDown { key: event.key, code: event.code, ctrl: event.ctrl_key }
),
online_status(key: "network", value: Msg::Changed {}),
online_status(
key: "network-payload",
handler: (_online: bool) -> Msg => Msg::Changed {}
),
window_on_resize(
key: "viewport",
handler: (_size: WindowSize) -> Msg => Msg::Changed {}
),
document_on_visibility_change(
key: "visibility",
handler: (_visibility: DocumentVisibility) -> Msg => Msg::Changed {}
),
location_on_change(
key: "location",
handler: (location: Location) -> Msg => Msg::LocationChanged { href: location.href }
),
window_on_focus(
key: "focus",
handler: (event: GenericEvent) -> Msg => Msg::WindowEvent { event: event.event }
),
window_on_blur(key: "blur", value: Msg::Changed {}),
animation_frame(
key: "frame",
handler: (frame: AnimationFrame) -> Msg => Msg::Frame { timestamp: frame.timestamp }
),
media_query(
query: "(prefers-reduced-motion: reduce)",
handler: (query: MediaQuery) -> Msg => Msg::Media { matches: query.matches }
),
storage_on_change(
key: "storage",
handler: (_change: StorageChange) -> Msg => Msg::Changed {}
),
Sub<Msg>::runtime_configured<String>(
kind: "websocket",
key: "socket",
value: msgpack::make_string("updates"),
handler: (value: String) -> Msg => Msg::Broadcast { value: value }
),
broadcast_channel<String, Msg>(
name: "updates",
handler: (value: String) -> Msg => Msg::Broadcast { value: value }
)
])

fn ctrl_label(value: bool) -> String
if
value: "ctrl"
else: "no-ctrl"
43 changes: 43 additions & 0 deletions apps/smoke/src/vx-dom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const valueArrayModelEntryPath = path.join(
);
const typedMouseEventEntryPath = path.join(fixtureRoot, "vx-typed-mouse-event.voyd");
const userProgramNameEntryPath = path.join(fixtureRoot, "vx-user-program-name.voyd");
const runtimeBrowserEntryPath = path.join(fixtureRoot, "vx-runtime-browser.voyd");
const wideValueModelEntryPath = path.join(fixtureRoot, "vx-wide-value-model.voyd");

const expectCompileSuccess = (
Expand Down Expand Up @@ -438,6 +439,48 @@ describe("smoke: compiled VX DOM rendering", () => {
expect(container.innerHTML).toBe("");
});

it("runs compiled browser runtime commands and payload subscriptions", async () => {
const writeText = vi.fn(async () => undefined);
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: { writeText },
});
const sdk = createSdk();
const result = expectCompileSuccess(await sdk.compile({ entryPath: runtimeBrowserEntryPath }));
const host = await createVoydHost({
wasm: result.wasm,
bufferSize: 256 * 1024,
});
const commands = await host.run<{ children?: unknown[] }>("standard_commands");
const subscriptions = await host.run<{ children?: unknown[] }>("standard_subscriptions");

expect(commands.children).toHaveLength(21);
expect(subscriptions.children).toHaveLength(14);

const app = createVoydVxAppRuntime({ host });

const container = document.createElement("div");
const mounted = await mountVxApp({ container, app });

expect(writeText).toHaveBeenCalledWith("Copied from Voyd");
expect(container.querySelector(".status")?.textContent).toBe("ready");

window.dispatchEvent(new KeyboardEvent("keydown", {
key: "s",
code: "KeyS",
ctrlKey: true,
}));
await nextTurn();

expect(container.querySelector(".status")?.textContent).toBe("key");
expect(container.querySelector(".key")?.textContent).toBe("s");
expect(container.querySelector(".code")?.textContent).toBe("KeyS");
expect(container.querySelector(".ctrl")?.textContent).toBe("ctrl");

mounted.dispose();
expect(container.innerHTML).toBe("");
});

it("marshals typed message variants with omitted optional fields", async () => {
const sdk = createSdk();
const result = expectCompileSuccess(await sdk.compile({
Expand Down
Loading
Loading