Files
ApiCore/ApiCore-Frontend/src/lib/hive/copy.spec.ts
T
Nick SzaunigandClaude Opus 5.5 c608f5d405 feat: hive card with SVG, outline, edit sheets and catalog settings (Beute L2)
- lib/hive: frontend port of the hive domain (ops, validator, facts,
  layout, follow-ups, describe texts, reason texts) running the backend
  fixtures, API client
- hive card on the colony page (same height as Stammdaten): SVG view,
  accessible outline tree, frame/part sheets with optimistic save,
  conflict sheet, history with past states, init from template or
  another colony, inspection draft mode
- follow-up toasts prefilling feeding and treatment forms (treatment
  end only with an open treatment)
- settings page "Beutensysteme" (hive-systems:manage)
- NumberStepper decimal steps, reactive motion level hook, holo tokens
- spec: owner design decision 2026-10-16 (glass hologram, focus mode)

The SVG view is the fallback; the 3D scene follows in L3.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 09:12:03 +02:00

60 lines
2.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { copyHiveFrom } from "./copy";
import { frames, inlineCatalog, part } from "./test-utils";
import type { HiveState } from "./types";
const catalog = inlineCatalog();
const source: HiveState = {
schemaVersion: 1,
stack: [
part("f", "floor", { label: "Mein Boden", inserts: [part("fi", "floor-insert")] }),
part("b", "box", {
label: "Brutraum",
frames: [
{ slot: 1, type: "BROOD", name: "Königin" },
{ slot: 2, type: "HONEY" },
{ slot: 3, type: "FOUNDATION" },
...frames(7, "NONE").map((frame) => ({ ...frame, slot: frame.slot + 3 })),
],
}),
part("fe", "feeder", { state: { syrupLiters: 2, fondantKg: 1 } }),
part("l", "inner-lid", { state: { feedHole: "CLOSED" } }),
],
};
describe("copyHiveFrom", () => {
const copy = copyHiveFrom(source, catalog);
it("resets levels to 0 and choices to the first option", () => {
expect(copy.stack[2].state).toEqual({ syrupLiters: 0, fondantKg: 0 });
expect(copy.stack[3].state).toEqual({ feedHole: "OPEN" });
});
it("removes part labels and frame names", () => {
expect(copy.stack[0].label).toBeUndefined();
expect(copy.stack[1].label).toBeUndefined();
expect(copy.stack[1].frames?.every((frame) => frame.name === undefined)).toBe(true);
});
it("turns every frame into an empty comb except foundations", () => {
const types = copy.stack[1].frames?.map((frame) => frame.type);
expect(types?.slice(0, 3)).toEqual(["EMPTY_COMB", "EMPTY_COMB", "FOUNDATION"]);
expect(types?.slice(3).every((type) => type === "EMPTY_COMB")).toBe(true);
expect(copy.stack[1].frames?.map((frame) => frame.slot)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
});
it("gives every part and insert a fresh id and keeps the stack order", () => {
const oldIds = ["f", "fi", "b", "fe", "l"];
const newIds = [copy.stack[0].id, copy.stack[0].inserts?.[0].id, copy.stack[1].id, copy.stack[2].id, copy.stack[3].id];
expect(new Set(newIds).size).toBe(5);
expect(newIds.some((id) => oldIds.includes(String(id)))).toBe(false);
expect(copy.stack.map((p) => p.partTypeId)).toEqual(["floor", "box", "feeder", "inner-lid"]);
expect(copy.stack[0].inserts?.[0].partTypeId).toBe("floor-insert");
});
it("does not change the source", () => {
expect(source.stack[1].frames?.[0]).toEqual({ slot: 1, type: "BROOD", name: "Königin" });
});
});