UNPKG

rvx

Version:

A signal based rendering library

231 lines (183 loc) 6.37 kB
# Testing Testing rvx based applications is usually very simple because all of its signal based rendering is synchronous. E.g. when updating a signal, all resulting changes are reflected in the DOM immediately: === "JSX" ```jsx import { $ } from "rvx"; const count = $(7); const element = <div>Current count: {count}</div> as HTMLDivElement; assert(element.innerText === "Current count: 7"); count.value = 42; assert(element.innerText === "Current count: 42"); ``` === "No Build" ```jsx import { $, e } from "./rvx.js"; const count = $(7); const element = e("div").append("Current count: ", count).elem; assert(element.innerText === "Current count: 7"); count.value = 42; assert(element.innerText === "Current count: 42"); ``` Note that the `assert` function used on this page is not included in rvx. ## Synchronous Tests Rvx provides a lightweight wrapper for running small synchronous tests that takes care of calling [teardown hooks](./core/lifecycle.md) after the test. === "JSX" ```jsx import { $ } from "rvx"; import { runTest, querySelector } from "rvx/test"; runTest(() => { const count = $(0); const view = mount( document.body, <button on:click={() => { count.value++; }}>Click me!</button>, ); querySelector(view, "button")?.click(); assert(count.value === 1); }); ``` === "No Build" ```jsx import { $ } from "./rvx.js"; import { runTest, querySelector } from "./rvx.test.js"; runTest(() => { const count = $(0); const view = mount( document.body, e("button").on("click", () => { count.value++; }).append("Click me!"), ); querySelector(view, "button")?.click(); assert(count.value === 1); }); ``` ## Asynchronous Tests There is a wrapper for async tests that allows you to run small synchronous parts of your test with a shared [async context](./async-utilities/async.md#tracking-completion). After the test, this will run [teardown hooks](./core/lifecycle.md) registered during any `use(..)` calls and wait for any pending tasks tracked in the async context. The example below shows a test that asserts that asynchronously loaded content is displayed correctly: === "JSX" ```jsx import { mount } from "rvx"; import { Async } from "rvx/async"; import { runAsyncTest, querySelector } from "rvx/test"; await runAsyncTest(async ({ asyncCtx, use }) => { const view = use(() => { return mount( document.body, <Async source={async () => { await something(); return "Hello World!"; }}> {content => <div class="page"> {content} </div>} </Async> ); }); // Wait for the "<Async>" component to resolve: await asyncCtx.complete(); const page = querySelector<HTMLElement>(view, ".page"); assert(page !== null); assert(page.innerText === "Hello World!"); }); ``` === "No Build" ```jsx import { e, mount } from "./rvx.js"; import { Async } from "./rvx.async.js"; import { runAsyncTest, querySelector } from "./rvx.test.js"; await runAsyncTest(async ({ asyncCtx, use }) => { const view = use(() => { return mount( document.body, Async({ source: async () => { await something(); return "Hello World!"; }, children: content => e("div").set("class", "page").append(content), }), ); }); // Wait for the "<Async>" component to resolve: await asyncCtx.complete(); const page = querySelector<HTMLElement>(view, ".page"); assert(page !== null); assert(page.innerText === "Hello World!"); }); ``` ## Waiting For Expressions You can watch arbitrary [expressions](./core/signals.md#expressions) using the `watchFor` function. === "JSX" ```jsx import { $ } from "rvx"; import { watchFor, isPending } from "rvx/async"; // Wait for a specific signal value: const count = $(0); setInterval(() => { count.value++ }, 1000); await watchFor(() => count.value > 7); // Wait with a timeout: await watchFor(() => count.value > 7, 500); // Wait for pending user tasks: await watchFor(() => !isPending()); ``` === "No Build" ```jsx import { $ } from "./rvx.js"; import { watchFor, isPending } from "./rvx.async.js"; // Wait for a specific signal value: const count = $(0); setInterval(() => { count.value++ }, 1000); await watchFor(() => count.value > 7); // Wait with a timeout: await watchFor(() => count.value > 7, 500); // Wait for pending user tasks: await watchFor(() => !isPending()); ``` ## Polling You can poll arbitrary functions for the first truthy result using the `poll` function. === "JSX" ```jsx import { poll } from "rvx/test"; // Poll a synchronous function: const heading = await poll(() => document.querySelector("h1")); console.log(heading.textContent); // Poll with a timeout: await poll(() => { ... }, 100); // Poll an async function: await poll(async abortSignal => { ... }); ``` === "No Build" ```jsx import { poll } from "./rvx.test.js"; // Poll a synchronous function: const heading = await poll(() => document.querySelector("h1")); console.log(heading.textContent); // Poll with a timeout: await poll(() => { ... }, 100); // Poll an async function: await poll(async abortSignal => { ... }); ``` !!! warning + Avoid overly expensive computations in the callback as it runs every event cycle. + Without a timeout, `poll` might run forever. ## Concurrency It is generally possible to run tests for rvx based applications concurrently. However, using APIs that may interfere with each other such as `Element.focus` can result in flaky tests. To solve this you can use the `exclusive` function to run code in a globally shared queue for a specific purpose: === "JSX" ```jsx import { runAsyncTest, exclusive } from "rvx/test"; const FOCUS_ACTIONS = Symbol("focus actions"); await exclusive(FOCUS_ACTIONS, async () => { someInput.focus(); await somethingElse(); assert(document.activeElement === someInput); }); ``` === "No Build" ```jsx import { runAsyncTest, exclusive } from "./rvx.test.js"; const FOCUS_ACTIONS = Symbol("focus actions"); await exclusive(FOCUS_ACTIONS, async () => { someInput.focus(); await somethingElse(); assert(document.activeElement === someInput); }); ``` Using symbols as keys that are in some common place in your test setup is recommended as it prevents any typos in the key, but you can also use anything else that can be a `Map` key.