Newer
Older
smart-home-server / webclient-vue / src / features / areas / components / __tests__ / AreaTreeNode.spec.js
import { describe, it, expect, vi, beforeEach } from "vitest";
import { mount } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { useAreasStore } from "../../../../stores/areas";
import { useFavoritesStore } from "../../../../stores/favorites";
import AreaTreeNode from "../AreaTreeNode.vue";

const pushMock = vi.fn();
vi.mock("vue-router", () => ({
  useRouter: () => ({ push: pushMock }),
}));

describe("AreaTreeNode", () => {
  beforeEach(() => {
    setActivePinia(createPinia());
    pushMock.mockClear();
  });

  function makeArea(overrides = {}) {
    return {
      id: 1,
      display_name: "Kitchen",
      type: "room",
      alias: "kitchen",
      parent_id: 0,
      children: [],
      ...overrides,
    };
  }

  it("renders area info", () => {
    const wrapper = mount(AreaTreeNode, {
      props: { area: makeArea() },
    });

    expect(wrapper.text()).toContain("Kitchen");
    expect(wrapper.text()).toContain("room");
    expect(wrapper.text()).toContain("kitchen");
  });

  it("renders children when expanded", async () => {
    const area = makeArea({
      children: [
        { id: 2, display_name: "Pantry", type: "room", alias: "pantry", parent_id: 1, children: [] },
      ],
    });

    const wrapper = mount(AreaTreeNode, {
      props: { area },
    });

    expect(wrapper.text()).not.toContain("Pantry");

    await wrapper.find(".tree-toggle").trigger("click");

    expect(wrapper.text()).toContain("Pantry");
  });

  it("toggles expansion via store", async () => {
    const areasStore = useAreasStore();
    const area = makeArea({
      children: [
        { id: 2, display_name: "Pantry", type: "room", alias: "pantry", parent_id: 1, children: [] },
      ],
    });

    const wrapper = mount(AreaTreeNode, {
      props: { area },
    });

    expect(areasStore.isNodeExpanded(1)).toBe(false);

    await wrapper.find(".tree-toggle").trigger("click");

    expect(areasStore.isNodeExpanded(1)).toBe(true);

    await wrapper.find(".tree-toggle").trigger("click");

    expect(areasStore.isNodeExpanded(1)).toBe(false);
  });

  it("navigates to area detail on card click", () => {
    const wrapper = mount(AreaTreeNode, {
      props: { area: makeArea() },
    });

    wrapper.find(".area-tree-card").trigger("click");

    expect(pushMock).toHaveBeenCalledWith({
      name: "area-detail",
      params: { id: "1" },
    });
  });

  it("does not navigate when clicking toggle", () => {
    const wrapper = mount(AreaTreeNode, {
      props: { area: makeArea({ children: [{ id: 2, display_name: "Child", type: "room", alias: "child", parent_id: 1, children: [] }] }) },
    });

    wrapper.find(".tree-toggle").trigger("click");

    expect(pushMock).not.toHaveBeenCalled();
  });

  it("disables toggle button for leaf nodes", () => {
    const wrapper = mount(AreaTreeNode, {
      props: { area: makeArea() },
    });

    const toggle = wrapper.find(".tree-toggle");
    expect(toggle.attributes("disabled")).toBeDefined();
    expect(toggle.text()).toBe("ยท");
  });

  it("shows cycle message when area is its own ancestor", () => {
    const wrapper = mount(AreaTreeNode, {
      props: { area: makeArea({ id: 1 }), ancestors: ["1"] },
    });

    expect(wrapper.text()).toContain("Cycle skipped");
  });

  it("toggles favorite via store", () => {
    const store = useFavoritesStore();
    const wrapper = mount(AreaTreeNode, {
      props: { area: makeArea() },
    });

    expect(store.has(1)).toBe(false);

    wrapper.find(".area-favorite-btn").trigger("click");

    expect(store.has(1)).toBe(true);
  });
});