Ctrl K
ComunidadGuías…

lib/board-dnd-dom.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// @vitest-environment jsdom

import { registerBoardRowHeights } from "./board-windowing";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
  captureBoardDragPreview,
  createBoardBoundsModifier,
  measureBoardDragBounds,
  measureBoardDropBundleHeight,
  measureBoardDropVisualTarget,
} from "./board-dnd";

function rect({
  height,
  left,
  top,
  width,
}: {
  height: number;
  left: number;
  top: number;
  width: number;
}) {
  return {
    bottom: top + height,
    height,
    left,
    right: left + width,
    top,
    width,
  } as DOMRect;
}

describe("board drag overlay", () => {
  afterEach(() => {
    document.body.replaceChildren();
    vi.restoreAllMocks();
  });

  it("reserves the full bundle using column-owned offscreen geometry", () => {
    const root = document.createElement("div"), column = document.createElement("div");
    column.dataset.boardColumnScroller = ""; column.dataset.boardColumnStatus = "done";
    column.style.rowGap = "8px"; root.append(column); document.body.append(root);
    const dispose = registerBoardRowHeights(column, (id) => id === "first" ? 300 : id === "second" ? 120 : null);
    try {
      expect(measureBoardDropBundleHeight({ root, activeIds: ["first", "second"], status: "done" })).toBe(428);
      expect(measureBoardDropBundleHeight({ root: document.createElement("div"), activeIds: ["first"], status: "done" })).toBeNull();
    } finally { dispose(); }
  });

  it("captures a static card without duplicate interactive identity", () => {
    document.body.innerHTML = `
      <div data-issue-id="issue" aria-pressed="true" role="button" tabindex="0" class="opacity-40">
        <label id="title">Card</label>
        <input aria-labelledby="title" />
      </div>
    `;

    const html = captureBoardDragPreview("issue");

    expect(html).toContain("Card");
    expect(html).toContain("pointer-events-none");
    expect(html).not.toContain("data-issue-id");
    expect(html).not.toContain("opacity-40");
    expect(html).not.toContain('id="title"');
    expect(html).not.toContain("aria-");
    expect(html).not.toContain("tabindex");
  });

  it("keeps duplicate retained issue IDs and destination columns outside the active board", () => {
    const hidden = document.createElement("div");
    const active = document.createElement("div");
    for (const [board, title, size] of [[hidden, "Hidden card", 900], [active, "Active card", 60]] as const) {
      const card = document.createElement("article");
      card.dataset.issueId = "issue";
      card.textContent = title;
      card.getBoundingClientRect = () => rect({ height: size, left: 20, top: 100, width: 280 });
      const column = document.createElement("div");
      column.dataset.boardColumnScroller = "";
      column.dataset.boardColumnStatus = "done";
      column.style.rowGap = "8px";
      column.getBoundingClientRect = () => rect({ height: 500, left: 350, top: 70, width: 300 });
      const marker = document.createElement("div");
      marker.dataset.boardDropIndicator = "";
      column.append(marker);
      board.append(card, column);
    }
    hidden.style.display = "none";
    document.body.append(hidden, active);
    expect(captureBoardDragPreview("issue", active)).toContain("Active card");
    expect(captureBoardDragPreview("issue", active)).not.toContain("Hidden card");
    expect(measureBoardDropBundleHeight({ root: active, activeIds: ["issue"], status: "done" })).toBe(60);
    expect(measureBoardDropVisualTarget({ root: active, activeId: "issue", activeIds: ["issue"], status: "done", bounds: null }))
      .toEqual({ height: 60, left: 350, top: 70, width: 300 });
    expect(captureBoardDragPreview("issue", null)).toBeNull();
  });

  it("measures the card viewport below column headers", () => {
    const board = document.createElement("div");
    const column = document.createElement("div");
    column.dataset.boardColumnScroller = "";
    board.appendChild(column);
    vi.spyOn(board, "getBoundingClientRect").mockReturnValue({
      left: 10,
      right: 610,
      top: 20,
      bottom: 620,
    } as DOMRect);
    vi.spyOn(column, "getBoundingClientRect").mockReturnValue({
      left: 10,
      right: 310,
      top: 70,
      bottom: 590,
    } as DOMRect);

    expect(measureBoardDragBounds(board)).toEqual({
      left: 10,
      right: 610,
      top: 70,
      bottom: 590,
    });
  });

  it("clamps the overlay inside cached bounds", () => {
    const modifier = createBoardBoundsModifier({
      current: { left: 0, right: 300, top: 50, bottom: 400 },
    });
    const transform = modifier({
      draggingNodeRect: {
        left: 20,
        right: 120,
        top: 100,
        bottom: 180,
      },
      transform: { x: -50, y: 300, scaleX: 1, scaleY: 1 },
    } as never);

    expect(transform).toEqual({ x: -19, y: 219, scaleX: 1, scaleY: 1 });
  });

  it("measures the rendered marker as an immediate cross-column target", () => {
    const source = document.createElement("article");
    source.dataset.issueId = "issue";
    source.getBoundingClientRect = () =>
      rect({ height: 60, left: 20, top: 100, width: 280 });
    const column = document.createElement("div");
    column.dataset.boardColumnScroller = "";
    column.dataset.boardColumnStatus = "done";
    column.style.rowGap = "8px";
    column.getBoundingClientRect = () =>
      rect({ height: 500, left: 350, top: 70, width: 300 });
    const marker = document.createElement("div");
    marker.dataset.boardDropIndicator = "";
    const anchor = document.createElement("article");
    anchor.getBoundingClientRect = () =>
      rect({ height: 70, left: 360, top: 200, width: 280 });
    column.append(marker, anchor);
    document.body.append(source, column);

    expect(
      measureBoardDropVisualTarget({
        activeId: "issue",
        activeIds: ["issue"],
        bounds: null,
        status: "done",
      }),
    ).toEqual({ height: 60, left: 360, top: 200, width: 280 });
  });

  it("removes the source slot when measuring a same-column reorder", () => {
    const column = document.createElement("div");
    column.dataset.boardColumnScroller = "";
    column.dataset.boardColumnStatus = "todo";
    column.style.rowGap = "8px";
    const source = document.createElement("article");
    source.dataset.issueId = "issue";
    source.getBoundingClientRect = () =>
      rect({ height: 60, left: 20, top: 100, width: 280 });
    const marker = document.createElement("div");
    marker.dataset.boardDropIndicator = "";
    const anchor = document.createElement("article");
    anchor.getBoundingClientRect = () =>
      rect({ height: 70, left: 20, top: 300, width: 280 });
    column.append(source, marker, anchor);
    document.body.append(column);

    expect(
      measureBoardDropVisualTarget({
        activeId: "issue",
        activeIds: ["issue"],
        bounds: null,
        status: "todo",
      })?.top,
    ).toBe(232);
  });

  it("measures an end-of-column marker without a footer anchor", () => {
    const source = document.createElement("article");
    source.dataset.issueId = "issue";
    source.getBoundingClientRect = () =>
      rect({ height: 60, left: 20, top: 100, width: 280 });
    const column = document.createElement("div");
    column.dataset.boardColumnScroller = "";
    column.dataset.boardColumnStatus = "done";
    column.style.rowGap = "8px";
    const previous = document.createElement("article");
    previous.getBoundingClientRect = () =>
      rect({ height: 70, left: 360, top: 200, width: 280 });
    const marker = document.createElement("div");
    marker.dataset.boardDropIndicator = "";
    column.append(previous, marker);
    document.body.append(source, column);

    expect(
      measureBoardDropVisualTarget({
        activeId: "issue",
        activeIds: ["issue"],
        bounds: null,
        status: "done",
      }),
    ).toEqual({ height: 60, left: 360, top: 278, width: 280 });
  });

  it("uses the content padding for an empty destination column", () => {
    const source = document.createElement("article");
    source.dataset.issueId = "issue";
    source.getBoundingClientRect = () =>
      rect({ height: 60, left: 20, top: 100, width: 280 });
    const column = document.createElement("div");
    column.dataset.boardColumnScroller = "";
    column.dataset.boardColumnStatus = "canceled";
    column.style.padding = "8px";
    column.getBoundingClientRect = () =>
      rect({ height: 500, left: 350, top: 70, width: 300 });
    const marker = document.createElement("div");
    marker.dataset.boardDropIndicator = "";
    column.append(marker);
    document.body.append(source, column);

    expect(
      measureBoardDropVisualTarget({
        activeId: "issue",
        activeIds: ["issue"],
        bounds: null,
        status: "canceled",
      }),
    ).toEqual({ height: 60, left: 358, top: 78, width: 284 });
  });

  it("reserves the complete bundle height including internal stack gaps", () => {
    const first = document.createElement("article");
    first.dataset.issueId = "first";
    first.getBoundingClientRect = () =>
      rect({ height: 60, left: 20, top: 100, width: 280 });
    const second = document.createElement("article");
    second.dataset.issueId = "second";
    second.getBoundingClientRect = () =>
      rect({ height: 80, left: 20, top: 168, width: 280 });
    const column = document.createElement("div");
    column.dataset.boardColumnScroller = "";
    column.dataset.boardColumnStatus = "done";
    column.style.rowGap = "8px";
    document.body.append(first, second, column);

    expect(
      measureBoardDropBundleHeight({
        activeIds: ["first", "second"],
        status: "done",
      }),
    ).toBe(148);
  });
});