- 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]>
60 lines
2.4 KiB
TypeScript
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" });
|
|
});
|
|
});
|