Сформулируйте наблюдаемое обещание
Обещание конкретно: после двух увеличений слева и одного справа сброс левой панели оставляет 0 и 1, а глобальное состояние — 0. Браузер должен проверить обе стороны. Тест только левой панели пропустит ошибочный сброс сразу всех областей.
Откройте проверочный эксперимент и сначала воспроизведите последовательность вручную. Здесь используются те же два редактора, что в главе об областях. Упражнение предлагает наблюдения, а не отметку автоматического прохождения.
Ниже — минимальная тестовая сцена для настроенного рабочего пространства Mercury. Она использует публичную функцию рендеринга и запросы @fiberfx/testing, а нативные нажатия выполняет внутри act. Это пример для адаптации, а не отчёт об успешном запуске вашей копии.
Выразите действия тестом
import {test, expect} from "vitest";
import {createElement as h} from "@fiberfx/hyperscript";
import {event, store, fork, dispose} from "@fiberfx/hypergraph";
import {Provider} from "@fiberfx/hypergraph/hyperscript";
import {reflect} from "@fiberfx/reflect";
import {act, render, cleanup} from "@fiberfx/testing";
test("reset changes only the selected workspace", async () => {
const added = event<void>();
const reset = event<void>();
const $count = store(0).on(added, n => n + 1).reset(reset);
const Counter = reflect({
view: (p: {name: string; count: number; add: () => void; clear: () => void}) =>
h("section", null,
h("p", null, `${p.name}: ${p.count}`),
h("button", {onClick: p.add}, `${p.name} add`),
h("button", {onClick: p.clear}, `${p.name} reset`)),
bind: {count: $count, add: () => added(), clear: () => reset()},
});
const left = fork();
const right = fork();
try {
const ui = render(h("div", null,
h(Provider, {value: left}, h(Counter, {name: "Left"})),
h(Provider, {value: right}, h(Counter, {name: "Right"})),
));
const click = (name: string) => act(() => ui.getByRole("button", {name}).click());
await click("Left add");
await click("Left add");
await click("Right add");
expect(ui.getByText("Left: 2")).toBeTruthy();
expect(ui.getByText("Right: 1")).toBeTruthy();
await click("Left reset");
expect(ui.getByText("Left: 0")).toBeTruthy();
expect(ui.getByText("Right: 1")).toBeTruthy();
expect($count.getState()).toBe(0);
} finally {
cleanup();
dispose(left);
dispose(right);
}
});
Сохраните пример, например как reflect-isolation.test.ts, в приложении или модуле, где mx разрешает зависимости Hyperscript, Hypergraph, Reflect и Testing совместимых версий. При нативном mx test импорт vitest обслуживает тестовая среда самого mx; отдельный runner Vitest для этого примера не требуется.
Запустите из настроенного модуля:
mx test
Нужен доступный браузер Chromium. Подготовка описана в главе инструменты mx. Отсутствие браузера, неразрешённый импорт, ноль тестов или потерянный результат не означают успех.
Прочитайте проверки как свидетельства
Первая пара проверок показывает, что действия выполнены в обеих панелях. Вторая — что сброс изменил цель и не затронул соседнюю область. Последнее чтение проверяет глобальную модель вне scope. Очистка размонтирует представления до освобождения областей, чтобы хук мог обращаться к ещё существующей области.
Сохраните имя теста и фактический результат своего запуска. Разделяйте три утверждения: пример задаёт ожидание, успешный браузерный тест измеряет эту сцену, а приёмка приложения требует проверки его реальной интеграции. Успешная проверка типов не заменяет наблюдение в браузере.
Добейтесь правильного отказа теста
В изолированной копии сцены намеренно выведите обе панели в одной области. Ожидания счётчиков должны нарушиться. Верните разные области и повторите запуск. Так вы проверите, что тест замечает именно ту ошибку владения, от которой должен защищать.
Добавьте отдельный отрицательный тест выполнения для одинаковых ключей списка или пересечения bind/mapProps. Ошибки типов проверяйте отдельно: неверный аргумент события, отсутствующее обязательное свойство строки, несовместимый дискриминатор объединения. Примеры есть в test/types.ts пакета; проверка TypeScript выполняется отдельно от mx test. Не удаляйте ожидание ради зелёного результата.
Не оставляйте состояние следующему тесту
Для новой ручной попытки перезапустите всё упражнение. В тестах создавайте свежие области и очищайте дерево даже при ошибке утверждения, как в блоке finally. Освобождайте также наблюдателей и ресурсы, которые добавляет ваша сцена.
Повторите сценарий со сбросом правой панели. Симметрия поможет обнаружить случайное особое поведение первой панели.
Защитите остальные границы
Добавляйте по одной проверке:
- Переопределите вычисляемое свойство, включая явный
undefined, и проверьте отсутствие вызова его проекции. - Переставьте строки и проверьте локальное состояние вместе с идентичностью DOM; удалите строку и проследите её очистку.
- Замените дочерний конструктор и проверьте намеренный сброс локального состояния; при перестановке сохраните постоянный callback ref.
- Выведите две области в HTML и проверьте разные результаты при отсутствии вызовов хуков.
Существующие тесты Reflect дают конкретные образцы, но не доказывают, что изменённое приложение уже прошло проверку. Для релиза нужны и проверки собранного пакета: тесты исходников в рабочем пространстве сами по себе не подтверждают содержимое опубликованного артефакта.
Теперь есть полный путь от модели к наблюдаемому поведению: привязать, вычислить, изолировать, собрать, проследить и проверить. Примените его к одному настоящему экрану, прежде чем добавлять новые абстракции.