Перейти к содержанию
Mercury
Русский
Дизайн книга
Меню
Русский

Reflect.8. Проверяем границу

Работающее нажатие — только половина проверки изоляции. Нужно увидеть, что изменилось, что сохранилось и обнаружит ли тест нарушение этой границы.

Reflect 4.1.1 · Интерактивное упражнение

Открыть упражнение на всю страницу

Сформулируйте наблюдаемое обещание

Обещание конкретно: после двух увеличений слева и одного справа сброс левой панели оставляет 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 дают конкретные образцы, но не доказывают, что изменённое приложение уже прошло проверку. Для релиза нужны и проверки собранного пакета: тесты исходников в рабочем пространстве сами по себе не подтверждают содержимое опубликованного артефакта.

Теперь есть полный путь от модели к наблюдаемому поведению: привязать, вычислить, изолировать, собрать, проследить и проверить. Примените его к одному настоящему экрану, прежде чем добавлять новые абстракции.

Назад · Курс · Далее