Hyperapp — это минималистичный фреймворк для построения реактивных веб-приложений на JavaScript. Его архитектура основывается на чистой функциональности, где состояние, действия и представление связаны через единую структуру. Основная идея — управление состоянием приложения через immutable state и чистые функции, что облегчает тестирование и предсказуемость поведения.
Состояние в Hyperapp представляет собой простой объект JavaScript. Все изменения состояния происходят через actions — функции, которые принимают текущее состояние и возвращают новый объект состояния. Компоненты и view функции остаются чистыми, что позволяет легко проверять их независимо от среды выполнения.
const state = { count: 0 };
const actions = {
increment: () => state => ({ count: state.count + 1 }),
decrement: () => state => ({ count: state.count - 1 })
};
const view = (state, actions) => (
h("div", {}, [
h("button", { onclick: actions.decrement }, "-"),
h("span", {}, state.count),
h("button", { onclick: actions.increment }, "+")
])
);
Основное направление тестирования в Hyperapp — проверка actions и их влияния на состояние. Поскольку actions — это чистые функции, их тестирование может выполняться без подключения к DOM или браузеру.
Пример теста с использованием Jest:
test("increment action increases count", () => {
const initialState = { count: 0 };
const newState = actions.increment()(initialState);
expect(newState.count).toBe(1);
});
Ключевой момент — изоляция действий от внешних эффектов. Любые асинхронные операции, такие как сетевые запросы, должны быть вынесены в отдельные функции, которые можно замокать или протестировать независимо.
Функция view в Hyperapp является чистой и зависит только
от состояния и действий. Тестирование view может выполняться через
сравнение возвращаемой структуры виртуального DOM с ожидаемой.
Пример теста view с использованием Jest и
hyperapp/h:
import { h } from "hyperapp";
test("view renders correct count", () => {
const state = { count: 5 };
const actionsMock = { increment: jest.fn(), decrement: jest.fn() };
const vdom = view(state, actionsMock);
expect(vdom.children[1].children[0]).toBe(5);
});
Такой подход позволяет убедиться, что отображение всегда соответствует текущему состоянию, не запуская браузер.
Для полноценного тестирования приложений на Hyperapp можно использовать E2E-тесты с Cypress или Playwright. Основная цель — проверить взаимодействие пользователя с приложением и корректность изменений состояния через реальные события DOM.
Пример сценария E2E:
cy.get("button").contains("+").click();
cy.get("span").should("contain", "1");
cy.get("button").contains("-").click();
cy.get("span").should("contain", "0");
Такой подход выявляет ошибки интеграции действий, состояния и представления, которые невозможно заметить при юнит-тестировании.
Hyperapp поддерживает side effects через эффекты
(effect). Для тестирования необходимо изолировать эти
эффекты и заменить их на моки.
Пример эффекта:
const fetchData = (url) => (state, actions) => {
fetch(url)
.then(response => response.json())
.then(data => actions.setData(data));
};
Тестирование можно проводить через моки fetch:
global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ name: "Test" }) })
);
test("fetchData effect calls setData", async () => {
const actionsMock = { setData: jest.fn() };
await fetchData("url")(null, actionsMock);
expect(actionsMock.setData).toHaveBeenCalledWith({ name: "Test" });
});
Изоляция эффектов позволяет тестировать логику приложения без реальных сетевых запросов или сторонних API.
Для сложных приложений на Hyperapp важно придерживаться нескольких стратегий:
Использование этих стратегий в совокупности обеспечивает надёжность и предсказуемость приложений на Hyperapp, облегчая сопровождение и расширение функционала без риска случайного разрушения существующей логики.
Такой подход позволяет построить тестируемое, масштабируемое и предсказуемое приложение, сохраняя минималистичную архитектуру Hyperapp.