fix: Beute-Editor und Durchsicht nach Browser-Prüfung
- Umbauen: Fokus nach Rückgängig/Wiederholen am Ende auf den anderen Verlaufsknopf, nach Entfernen auf die Nachbarzeile (sonst Überschrift) - Umbauen: Hinweis „Beute wurde inzwischen geändert“ zuverlässig, der Stand wird bei Fokus/Sichtbarkeit und alle 30 s neu geladen, solange ein Entwurf besteht; Editor-Wurzel ist <main> - Verlauf: Futtertaschen-Füllstand als „Flüssigfutter 1,5 l“, Entfernen als „Futtertasche entfernt“; Gliederung ohne leere Werte („Boden · /“) - Durchsicht: Rückgängig der letzten Änderung behält den Schalter „Liste“ und setzt den Fokus auf die Überschrift der Änderungen Refs #21 #22 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
7a07e1cac4
commit
8166e7c621
12 files changed
+253
-36
No files matched your search
@@ -243,6 +243,51 @@ describe("HiveEditorPage", () => {
|
||||
expect(fetchHive).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("moves focus to the other history button when undo or redo runs out", async () => {
|
||||
renderPage();
|
||||
await addPart("cover");
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Rückgängig" }));
|
||||
expect(screen.getByRole("button", { name: "Rückgängig" })).toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Wiederholen" })).toHaveFocus();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Wiederholen" }));
|
||||
expect(screen.getByRole("button", { name: "Wiederholen" })).toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Rückgängig" })).toHaveFocus();
|
||||
});
|
||||
|
||||
it("moves focus to the next row after removing a part, else the previous one", async () => {
|
||||
renderPage();
|
||||
const stack = await screen.findByRole("list", { name: "Aufbau" });
|
||||
|
||||
await userEvent.click(within(stack).getByRole("button", { name: "Entfernen: Futterzarge 2" }));
|
||||
expect(document.activeElement).toHaveAttribute("data-part-id", "fe");
|
||||
|
||||
// The floor insert is the last row: focus falls back to the row above (the floor).
|
||||
await userEvent.click(within(stack).getByRole("button", { name: "Entfernen: Bausperre" }));
|
||||
expect(document.activeElement).toHaveAttribute("data-part-id", "f");
|
||||
});
|
||||
|
||||
it("refreshes the setup on window focus and hints when the server moved on", async () => {
|
||||
renderPage();
|
||||
await addPart("cover");
|
||||
const other = sampleState();
|
||||
other.stack = other.stack.filter((p) => p.id !== "b2");
|
||||
vi.mocked(fetchHive).mockResolvedValue(setupView(other, "colony-1", 4));
|
||||
const calls = vi.mocked(fetchHive).mock.calls.length;
|
||||
|
||||
act(() => void window.dispatchEvent(new Event("focus")));
|
||||
|
||||
expect(await screen.findByText(/Die Beute wurde inzwischen geändert\. Beim Speichern/)).toBeInTheDocument();
|
||||
expect(vi.mocked(fetchHive).mock.calls.length).toBeGreaterThan(calls);
|
||||
});
|
||||
|
||||
it("is a main landmark", async () => {
|
||||
renderPage();
|
||||
|
||||
expect(await screen.findByRole("main")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("announces undo and redo", async () => {
|
||||
renderPage();
|
||||
await addPart("cover");
|
||||
|
||||
@@ -18,7 +18,9 @@ export default function HiveEditorPage() {
|
||||
const connectivity = useConnectivity();
|
||||
const backHref = `/colonies/${encodeURIComponent(id)}`;
|
||||
const colony = useQuery({ queryKey: ["colonies", id], queryFn: () => fetchColony(id) });
|
||||
const hive = useQuery({ queryKey: hiveKeys.detail(id), queryFn: () => fetchHive(id),
|
||||
const hive = useQuery({
|
||||
queryKey: hiveKeys.detail(id),
|
||||
queryFn: () => fetchHive(id),
|
||||
// A colony without a hive answers 404 for good; no point retrying.
|
||||
retry: (count, error) => hiveErrorCode(error) !== "HIVE_NOT_FOUND" && count < 3,
|
||||
});
|
||||
@@ -72,6 +74,7 @@ export default function HiveEditorPage() {
|
||||
setup={hive.data}
|
||||
catalog={catalog}
|
||||
canManageTemplates={hasPermission("hive-systems:manage")}
|
||||
onRefresh={() => void hive.refetch()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -140,17 +140,19 @@ export function InspectionHiveSection({
|
||||
catalog,
|
||||
errorText,
|
||||
}: InspectionHiveSectionProps) {
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
const headingRef = useRef<HTMLHeadingElement>(null);
|
||||
const [outlineShown, setOutlineShown] = useState(false);
|
||||
const [announcement, setAnnouncement] = useState("");
|
||||
return (
|
||||
// A portal form inside the card (frame/part sheet) must never submit the enclosing inspection form.
|
||||
<div
|
||||
ref={sectionRef}
|
||||
data-testid="hive-section"
|
||||
tabIndex={-1}
|
||||
className="flex flex-col gap-4 outline-none"
|
||||
onSubmit={(event) => event.stopPropagation()}
|
||||
>
|
||||
<h3 ref={headingRef} tabIndex={-1} className="sr-only">
|
||||
Beute-Änderungen
|
||||
</h3>
|
||||
<p role="status" aria-live="polite" className="sr-only">
|
||||
{announcement}
|
||||
</p>
|
||||
@@ -162,6 +164,8 @@ export function InspectionHiveSection({
|
||||
mode={canEdit ? "inspection" : "view"}
|
||||
onOpsChange={canEdit ? onOpsChange : undefined}
|
||||
initialOps={canEdit ? ops : undefined}
|
||||
initialShowOutline={outlineShown}
|
||||
onShowOutlineChange={setOutlineShown}
|
||||
/>
|
||||
</div>
|
||||
{canEdit && ops.length > 0 && base && catalog ? (
|
||||
@@ -171,7 +175,7 @@ export function InspectionHiveSection({
|
||||
catalog={catalog}
|
||||
onChange={onOpsReplace}
|
||||
onUndone={(text) => setAnnouncement(`Rückgängig: ${text}`)}
|
||||
onEmptied={() => sectionRef.current?.focus()}
|
||||
onEmptied={() => headingRef.current?.focus()}
|
||||
/>
|
||||
) : null}
|
||||
{errorText ? (
|
||||
|
||||
@@ -50,10 +50,16 @@ vi.mock("@/lib/hive/api", async (importOriginal) => ({
|
||||
fetchHiveSystems: vi.fn(),
|
||||
}));
|
||||
|
||||
const card = vi.hoisted(() => ({ renders: [] as { mode?: string; initialOps?: unknown[] }[] }));
|
||||
const card = vi.hoisted(() => ({ renders: [] as { mode?: string; initialOps?: unknown[]; initialShowOutline?: boolean }[] }));
|
||||
vi.mock("@/components/hive/hive-card", () => ({
|
||||
HiveCard: (props: { mode?: string; initialOps?: unknown[]; onOpsChange?: (ops: unknown[]) => void }) => {
|
||||
card.renders.push({ mode: props.mode, initialOps: props.initialOps });
|
||||
HiveCard: (props: {
|
||||
mode?: string;
|
||||
initialOps?: unknown[];
|
||||
initialShowOutline?: boolean;
|
||||
onOpsChange?: (ops: unknown[]) => void;
|
||||
onShowOutlineChange?: (shown: boolean) => void;
|
||||
}) => {
|
||||
card.renders.push({ mode: props.mode, initialOps: props.initialOps, initialShowOutline: props.initialShowOutline });
|
||||
const fill = { op: "setState", id: "fe", key: "syrupLiters", value: 2 };
|
||||
const frame = { op: "setFrame", boxId: "b1", slot: 5, type: "BROOD", name: null };
|
||||
const add = {
|
||||
@@ -73,6 +79,9 @@ vi.mock("@/components/hive/hive-card", () => ({
|
||||
<button type="button" onClick={() => props.onOpsChange?.([add, fillNew])}>
|
||||
Test neue Zarge
|
||||
</button>
|
||||
<button type="button" onClick={() => props.onShowOutlineChange?.(true)}>
|
||||
Test Liste
|
||||
</button>
|
||||
<button type="button" onClick={() => props.onOpsChange?.([fill, fill])}>
|
||||
Test zwei gleiche
|
||||
</button>
|
||||
@@ -217,7 +226,7 @@ describe("NewInspectionPage: section Beute", () => {
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: /^Rückgängig/ }));
|
||||
|
||||
expect(screen.getByTestId("hive-section")).toHaveFocus();
|
||||
expect(screen.getByRole("heading", { name: "Beute-Änderungen" })).toHaveFocus();
|
||||
});
|
||||
|
||||
it("keeps focus on a row when identical ops renumber their keys", async () => {
|
||||
@@ -229,6 +238,16 @@ describe("NewInspectionPage: section Beute", () => {
|
||||
expect(screen.getByRole("button", { name: /^Rückgängig/ })).toHaveFocus();
|
||||
});
|
||||
|
||||
it("keeps the card's Liste toggle across the remount after Rückgängig", async () => {
|
||||
renderPage();
|
||||
await userEvent.click(await screen.findByRole("button", { name: "Test Futter" }));
|
||||
await userEvent.click(screen.getByRole("button", { name: "Test Liste" }));
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: /^Rückgängig/ }));
|
||||
|
||||
expect(card.renders.at(-1)?.initialShowOutline).toBe(true);
|
||||
});
|
||||
|
||||
it("hides a refusal once the ops changed", async () => {
|
||||
vi.mocked(createInspection).mockRejectedValue(
|
||||
new ApiError(403, "Forbidden", { code: "HIVE_FORBIDDEN" }),
|
||||
|
||||
Reference in new issue
Block a user