rvx
Version:
A signal based rendering library
152 lines (142 loc) • 3.62 kB
JavaScript
/*!
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { View, ENV, leak, captureSelf, viewNodes } from './rvx.js';
import { Queue, AsyncContext, ASYNC } from './rvx.async.js';
function assertViewState(view) {
if (!(view instanceof View)) {
throw new Error("view is not a View");
}
const env = ENV.current;
if (!(view.first instanceof env.Node)) {
throw new Error("view.first is not a Node");
}
if (!(view.last instanceof env.Node)) {
throw new Error("view.last is not a Node");
}
if (view.first !== view.last) {
const parent = view.first.parentNode;
if (!(parent instanceof env.Node)) {
throw new Error("view.first.parent is not a Node");
}
let node = view.first.nextSibling;
nodes: for (;;) {
if (node === null) {
throw new Error("view is unterminated");
}
if (!(node instanceof env.Node)) {
throw new Error("view contains non-Node");
}
if (node === view.last) {
break nodes;
}
node = node.nextSibling;
}
}
}
class PollTimeoutError extends Error {
}
async function poll(fn, timeout) {
const ac = new AbortController();
let timer;
if (timeout !== undefined) {
timer = setTimeout(() => ac.abort(new PollTimeoutError()), timeout);
}
try {
for (;;) {
const value = await fn(ac.signal);
if (value) {
return value;
}
ac.signal.throwIfAborted();
await new Promise(r => setTimeout(r, 0));
}
}
finally {
clearTimeout(timer);
}
}
const KEY = Symbol.for("rvx:test:queues");
const QUEUES = globalThis[KEY] ?? (globalThis[KEY] = new Map());
function exclusive(key, action) {
let queue = QUEUES.get(key);
if (queue === undefined) {
queue = leak(() => new Queue());
QUEUES.set(key, queue);
}
return queue.block(action);
}
async function runAsyncTest(fn) {
const teardown = [];
const asyncCtx = new AsyncContext();
async function cleanup() {
for (let i = teardown.length - 1; i >= 0; i--) {
teardown[i]();
}
return asyncCtx.complete();
}
try {
const result = await fn({
asyncCtx,
use: fn => captureSelf(dispose => {
teardown.push(dispose);
return ASYNC.provide(asyncCtx, fn);
}),
});
await cleanup();
return result;
}
catch (error) {
try {
await cleanup();
}
catch { }
throw error;
}
}
function runTest(fn) {
return captureSelf(dispose => {
try {
return fn();
}
finally {
dispose();
}
});
}
function querySelector(view, selector) {
for (const node of viewNodes(view)) {
if (node.nodeType === 1) {
if (node.matches(selector)) {
return node;
}
const elem = node.querySelector(selector);
if (elem !== null) {
return elem;
}
}
}
return null;
}
function querySelectorAll(view, selector) {
const elems = [];
for (const node of viewNodes(view)) {
if (node.nodeType === 1) {
if (node.matches(selector)) {
elems.push(node);
}
elems.push(...node.querySelectorAll(selector));
}
}
return elems;
}
export { PollTimeoutError, assertViewState, exclusive, poll, querySelector, querySelectorAll, runAsyncTest, runTest };