Skip to content

Testing and demo data

You never need a Home Assistant to work on a control. @dashhaus/core/testing puts a real store over a fake transport for tests, and @dashhaus/core/demo puts it over a whole demo house for stories and trying things out.

renderConnected renders your component under a real store and HaProvider, on a transport that serves the entities you give it and records every service call. Your component goes through the real hook, mapper and actions; only Home Assistant is fake.

LightButton.tsx
import { useHaLight } from "@dashhaus/core";
export function LightButton({ entityId }: { entityId: string }) {
const { entity, actions } = useHaLight(entityId, { loadHistory: false });
return (
<button type="button" aria-pressed={entity.state === "on"} disabled={entity.state === "unavailable"} onClick={actions.onToggle}>
{entity.name}
</button>
);
}
LightButton.test.tsx
import { act, fireEvent, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { renderConnected } from "@dashhaus/core/testing";
import { LightButton } from "./LightButton";
const entities = {
"light.desk": { entity_id: "light.desk", state: "off", attributes: { friendly_name: "Desk" } },
};
describe("LightButton", () => {
it("toggles the light through Home Assistant", async () => {
const { calls } = renderConnected(<LightButton entityId="light.desk" />, entities);
fireEvent.click(await screen.findByRole("button", { name: "Desk" }));
expect(calls).toEqual([{ domain: "light", service: "toggle", data: { entity_id: "light.desk" } }]);
});
it("follows the entity as Home Assistant changes it", async () => {
const { emitEntity } = renderConnected(<LightButton entityId="light.desk" />, entities);
const button = await screen.findByRole("button", { name: "Desk" });
act(() => emitEntity("light.desk", { state: "on" }));
expect(button.getAttribute("aria-pressed")).toBe("true");
act(() => emitEntity("light.desk", { state: "unavailable" }));
expect(button).toHaveProperty("disabled", true);
});
});

calls is every callService in order, as { domain, service, data }. emitEntity patches one entity the way Home Assistant would report a change: top-level fields replace, attributes merge. emit replaces the whole set. These tests run with the rest of Dashhaus’s on every change, so the sample above is known to pass.

fakeTransport(initialEntities: HassEntities): { transport, callService, calls, emit, emitEntity }
renderConnected(ui: ReactNode, entities: HassEntities): { transport, callService, calls, emit, emitEntity, container, baseElement, debug, … }

@dashhaus/core/testing needs Vitest and Testing Library in your project; the rest of the package doesn’t. fakeTransport on its own gives you the transport to put behind createHaStore when you render some other way.

A skin’s card takes its model as props, so it needs no store at all: render it with an entity object you write. The raw Home Assistant states the package’s own tests use are in @dashhaus/core/fixtures/*, if you’d rather start from real attribute shapes, and the mappers (lightFromHass) turn them into models.

createDemoStore() is the store over a demo house: every kind of entity, with an area, today’s history, and actions that answer the way Home Assistant would. Storybook’s connected stories run on these demo transports.

import { HaProvider } from "@dashhaus/core";
import { createDemoCoverHaTransport, createDemoStore } from "@dashhaus/core/demo";
import { HaCoverTile } from "@dashhaus/orb";
/** The whole demo house, as Storybook and the docs' live dashboards run it. */
export const house = createDemoStore();
/** One domain's demo on its own: a smaller seed, for a story or a test about covers. */
export const covers = createDemoStore(createDemoCoverHaTransport());
export function CoverPlayground() {
return (
<HaProvider store={covers}>
<HaCoverTile entityId="cover.living_room" />
</HaProvider>
);
}

Each domain also has a smaller transport of its own (createDemoHaTransport for lights, createDemoCoverHaTransport, createDemoAlarmHaTransport and so on) for a story about one thing.

The demo’s entities and their states can change in any release. Test against entities you write, not against the demo.