Hyperapp — это компактный и функционально ориентированный JavaScript-фреймворк для создания пользовательских интерфейсов. В отличие от крупных библиотек, таких как React или Vue, Hyperapp сохраняет минимализм и простоту архитектуры, что накладывает особенности на процесс тестирования. Основной единицей работы здесь являются view-функции, которые возвращают описание DOM в виде виртуального дерева, и actions, управляющие состоянием приложения.
Тестирование в Hyperapp разделяется на несколько ключевых направлений:
View-функции в Hyperapp принимают state и возвращают виртуальное DOM-дерево. Такие функции можно тестировать полностью изолированно, без необходимости запуска браузера.
Пример view-функции:
const Counter = (state) => (
<div>
<h1>{state.count}</h1>
<button oncl ick={state.increment}>+</button>
<button oncl ick={state.decrement}>-</button>
</div>
);
Подход к тестированию:
Пример теста с использованием Jest:
import { h } from "hyperapp";
import { Counter } from "./Counter";
test("Counter отображает правильное значение", () => {
const state = { count: 5 };
const view = Counter(state);
expect(view.tag).toBe("div");
expect(view.children[0].children[0]).toBe(5);
expect(view.children[1].tag).toBe("button");
});
Ключевой момент — Hyperapp предоставляет виртуальное представление DOM, которое легко анализировать как обычный объект JavaScript. Для сложных компонентов можно рекурсивно проверять все дочерние узлы.
Actions в Hyperapp отвечают за изменение состояния и могут включать побочные эффекты (effects). Их тестирование разделяется на два подхода:
const increment = (state) => ({ ...state, count: state.count + 1 });
test("increment увеличивает count", () => {
const state = { count: 0 };
const newState = increment(state);
expect(newState.count).toBe(1);
});
const fetchData = (state) => [
state,
fetch("/data").then((res) => res.json())
];
test("fetchData вызывает fetch", async () => {
global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ key: "value" }) })
);
const [state, effect] = fetchData({ items: [] });
await effect;
expect(global.fetch).toHaveBeenCalledWith("/data");
});
Такой подход позволяет полностью изолировать логику изменения состояния и не зависеть от внешних сервисов.
Для комплексного тестирования компонентов, когда важно проверить взаимодействие view и actions, применяются тестовые рендереры.
Использование jsdom для эмуляции DOM:
import { app, h } from "hyperapp";
import { Counter } from "./Counter";
import { JSDOM } from "jsdom";
const dom = new JSDOM(`<!DOCTYPE html><body><div id="root"></div></body>`);
global.document = dom.window.document;
const state = { count: 0, increment: (s) => ({ ...s, count: s.count + 1 }) };
app({ init: state, view: Counter, node: document.getElementById("root") });
// Проверка DOM
const buttons = document.querySelectorAll("button");
buttons[0].click();
expect(document.querySelector("h1").textContent).toBe("1");
Интеграционные тесты позволяют проверить реальное поведение компонента в браузероподобной среде, что особенно важно для интерактивных элементов и асинхронных действий.
Тщательное тестирование позволяет не только обнаружить ошибки, но и документировать ожидаемое поведение компонентов, что особенно важно в крупных Hyperapp-приложениях с множеством состояний и взаимодействий.