/* eslint-disable sonarjs/no-duplicate-string */ import * as React from "react"; import { render, fireEvent, screen, act, createEvent, cleanup } from "@testing-library/react"; import { CompactSelection, DataEditor, type DataEditorProps, type GridCell, GridCellKind, type GridSelection, isSizedGridColumn, type Item, } from "../src/index.js"; import type { CustomCell, SizedGridColumn } from "../src/internal/data-grid/data-grid-types.js"; import type { DataEditorRef } from "../src/data-editor/data-editor.js"; import { assert } from "../src/common/support.js"; import { vi, type Mock, expect, describe, test, beforeEach, afterEach } from "vitest"; import type { GridKeyEventArgs } from "../src/internal/data-grid/event-args.js"; const BOOLEAN_DATA_LOOKUP: (boolean | null | undefined)[] = [true, false, undefined, null]; function getMockBooleanData(row: number): boolean | null | undefined { return BOOLEAN_DATA_LOOKUP[row % BOOLEAN_DATA_LOOKUP.length]; } function sendClick(el: Element | Node | Document | Window, options?: any, runTimers?: boolean): void { fireEvent.mouseDown(el, options); if (runTimers === true) vi.runAllTimers(); fireEvent.mouseUp(el, options); if (runTimers === true) vi.runAllTimers(); fireEvent.click(el, options); } function sendTouchClick(el: Element | Node | Document | Window, options?: any): void { fireEvent.touchStart(el, options); fireEvent.touchEnd(el, { ...options, changedTouches: options.touches, }); fireEvent.click(el, { clientX: options?.touches?.[0]?.clientX, clientY: options?.touches?.[0]?.clientY, pointerType: "touch", ...options, }); } const makeCell = (cell: Item): GridCell => { const [col, row] = cell; switch (col) { case 0: { return { kind: GridCellKind.RowID, allowOverlay: false, data: `Data: ${col}, ${row}`, }; } case 3: { return { kind: GridCellKind.Number, allowOverlay: true, data: 10, displayData: `${row}`, }; } case 4: { return { kind: GridCellKind.Drilldown, allowOverlay: false, data: [ { img: "https://cdn.pixabay.com/photo/2017/02/20/18/03/cat-2083492_1280.jpg", text: "Foobar", }, ], }; } case 5: { return { kind: GridCellKind.Protected, allowOverlay: false, }; } case 6: { return { kind: GridCellKind.Bubble, allowOverlay: false, data: ["Foobar"], }; } case 7: { return { kind: GridCellKind.Boolean, allowOverlay: false, data: getMockBooleanData(row), readonly: row === 5, }; } case 8: { return { kind: GridCellKind.Text, allowOverlay: true, data: `Data: ${col}, ${row}`, displayData: `שלום ${col}, ${row}`, }; } case 9: { return { kind: GridCellKind.Markdown, allowOverlay: true, data: `# Header: ${col}, ${row}`, }; } case 10: { return { kind: GridCellKind.Uri, allowOverlay: true, data: `https://example.com/${col}/${row}`, }; } // No default } if (col > 10) { throw new Error(`Unexpected column: ${col}`); } return { kind: GridCellKind.Text, allowOverlay: true, data: `Data: ${col}, ${row}`, displayData: `${col}, ${row}`, allowWrapping: true, }; }; const basicProps: DataEditorProps = { columns: [ { title: "A", width: 150, icon: "headerRowID", }, { title: "B", width: 160, icon: "headerCode", }, { title: "C", width: 170, icon: "headerNumber", }, { title: "D", width: 180, icon: "headerString", }, { title: "E", width: 40, icon: "headerBoolean", }, { title: "F", width: 50, icon: "headerUri", }, { title: "G", width: 60, icon: "headerVideoUri", }, { title: "H", width: 70, icon: "headerEmoji", }, { title: "I", width: 80, icon: "headerImage", }, { title: "J", width: 90, icon: "headerPhone", }, { title: "K", width: 90, icon: "headerPhone", }, ], getCellContent: makeCell, getCellsForSelection: true, groupHeaderHeight: 32, headerHeight: 36, rowHeight: 32, onRowAppended: () => undefined, trailingRowOptions: { hint: "New row", sticky: true, tint: true, }, rows: 1000, }; function getCellCenterPositionForDefaultGrid(cell: Item): [number, number] { const [col, row] = cell; const xStart = basicProps.columns.slice(0, col).reduce((acc, curr) => acc + (curr as SizedGridColumn).width, 0); const xOffset = (basicProps.columns[col] as SizedGridColumn).width / 2; const yStart = (basicProps.headerHeight as number) + row * (basicProps.rowHeight as number); const yOffset = (basicProps.rowHeight as number) / 2; return [xStart + xOffset, yStart + yOffset]; } function prep(resetTimers: boolean = true) { const scroller = document.getElementsByClassName("dvn-scroller").item(0); if (scroller !== null) { vi.spyOn(scroller, "clientWidth", "get").mockImplementation(() => 1000); vi.spyOn(scroller, "clientHeight", "get").mockImplementation(() => 1000); vi.spyOn(scroller, "offsetWidth" as any, "get").mockImplementation(() => 1000); vi.spyOn(scroller, "offsetHeight" as any, "get").mockImplementation(() => 1000); } act(() => { vi.runAllTimers(); }); if (resetTimers) { vi.useRealTimers(); } else { act(() => { vi.runAllTimers(); }); } return scroller; } const Context: React.FC = p => { return ( <> {p.children}
); }; // eslint-disable-next-line react/display-name const EventedDataEditor = React.forwardRef((p, ref) => { const [sel, setSel] = React.useState(p.gridSelection); const [extraRows, setExtraRows] = React.useState(0); const onGridSelectionChange = React.useCallback( (s: GridSelection) => { setSel(s); p.onGridSelectionChange?.(s); }, [p] ); const onRowAppened = React.useCallback(() => { setExtraRows(cv => cv + 1); void p.onRowAppended?.(); }, [p]); return ( ); }); describe("data-editor", () => { vi.mock("../src/common/resize-detector", () => { return { useResizeDetector: () => ({ ref: undefined, width: 1000, height: 1000 }), }; }); // beforeAll(() => { // vi.spyOn(globalThis, "requestAnimationFrame").mockImplementation((callback: FrameRequestCallback) => { // return setTimeout(callback, 10); // }); // }); // afterAll(() => { // vi.restoreAllMocks(); // }); beforeEach(() => { // delete (window as any).ResizeObserver; // window.ResizeObserver = vi.fn().mockImplementation(() => ({ // observe: vi.fn(), // unobserve: vi.fn(), // disconnect: vi.fn(), // })); Element.prototype.scrollTo = vi.fn() as any; Element.prototype.scrollBy = vi.fn() as any; Object.assign(navigator, { clipboard: { writeText: vi.fn(() => Promise.resolve()), readText: vi.fn(() => Promise.resolve(`Sunday Dogs https://google.com Monday Cats https://google.com Tuesday Turtles https://google.com Wednesday Bears https://google.com Thursday "L ions" https://google.com Friday Pigs https://google.com Saturday "Turkeys and some ""quotes"" and a new line char ""more quotes"" plus a tab ." https://google.com`) ), }, }); Element.prototype.getBoundingClientRect = () => ({ bottom: 1000, height: 1000, left: 0, right: 1000, top: 0, width: 1000, x: 0, y: 0, toJSON: () => "", }); Object.defineProperties(HTMLElement.prototype, { offsetWidth: { get() { return 1000; }, }, }); Image.prototype.decode = vi.fn(); }); afterEach(() => { vi.clearAllTimers(); cleanup(); }); test("Focus a11y cell", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const a11ycell = screen.getByTestId("glide-cell-0-5"); fireEvent.focus(a11ycell); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [0, 5] }), }) ); }); test("Click a11y cell", async () => { vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const a11ycell = screen.getByTestId("glide-cell-0-5"); fireEvent.click(a11ycell); }); test("emits contextmenu for cell", async () => { const spy = vi.fn(); const spySelection = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); const scroller = prep(); assert(scroller !== null); screen.getByTestId("data-grid-canvas"); fireEvent.contextMenu(scroller, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); expect(spySelection).toHaveBeenCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [1, 1] }), }) ); }); test("emits contextmenu for row marker", async () => { const spy = vi.fn(); const spySelection = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); const scroller = prep(); assert(scroller !== null); screen.getByTestId("data-grid-canvas"); fireEvent.contextMenu(scroller, { clientX: 10, // Row marker clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalledWith([-1, 1], expect.anything()); }); test("emits contextmenu for cell but does not change selection if already selected - rows", async () => { const spy = vi.fn(); const spySelection = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); const scroller = prep(); assert(scroller !== null); screen.getByTestId("data-grid-canvas"); fireEvent.contextMenu(scroller, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); expect(spySelection).not.toHaveBeenCalled(); }); test("emits contextmenu for cell but does not change selection if already selected - cols", async () => { const spy = vi.fn(); const spySelection = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); const scroller = prep(); assert(scroller !== null); screen.getByTestId("data-grid-canvas"); fireEvent.contextMenu(scroller, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); expect(spySelection).not.toHaveBeenCalled(); }); test("middle click does not change selection", async () => { const spySelection = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) button: 1, }); expect(spySelection).not.toHaveBeenCalled(); }); test("emits contextmenu for cell but does not change selection if already selected - current.cell", async () => { const spy = vi.fn(); const spySelection = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); const scroller = prep(); assert(scroller !== null); screen.getByTestId("data-grid-canvas"); fireEvent.contextMenu(scroller, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); expect(spySelection).not.toHaveBeenCalled(); }); test("emits contextmenu for cell but does not change selection if already selected - current.range", async () => { const spy = vi.fn(); const spySelection = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); const scroller = prep(); assert(scroller !== null); screen.getByTestId("data-grid-canvas"); fireEvent.contextMenu(scroller, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); expect(spySelection).not.toHaveBeenCalled(); }); test("emits contextmenu for cell row markers", async () => { const spy = vi.fn(); const spySelection = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); const scroller = prep(); assert(scroller !== null); screen.getByTestId("data-grid-canvas"); fireEvent.contextMenu(scroller, { clientX: 320, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); expect(spySelection).toHaveBeenCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [1, 1] }), }) ); }); test("Emits cell click", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); }); test("Emits cell clicked with middle button", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { button: 1, clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); }); test("Does not emits cell clicked with back button", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { button: 4, clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).not.toHaveBeenCalled(); }); test("Emits cell click with touch", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendTouchClick(canvas, { touches: [ { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }, ], }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); }); test("Emits activated event on double click", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1]); }); describe("cellActivationBehavior", () => { test("double-click in time", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); vi.advanceTimersByTime(400); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1]); }); test("double-click miss", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); vi.advanceTimersByTime(600); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).not.toHaveBeenCalled(); }); test("second-click", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); vi.advanceTimersByTime(1600); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1]); }); test("single-click", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick( canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }, true ); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1]); }); }); test("Does not emit activated event on double click with different buttons", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { button: 0, clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); sendClick(canvas, { button: 1, clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).not.toHaveBeenCalled(); }); test("Emits activated event on Enter key", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { key: "Enter", }); vi.runAllTimers(); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1]); }); test("Toggle boolean with Enter key", async () => { const spy = vi.fn(); const editSpy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 850, // Col Boolean clientY: 36 * 2 + 32 + 16, // Row 2 (0 indexed) }); fireEvent.keyDown(canvas, { key: "Enter", }); vi.runAllTimers(); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([7, 2]); expect(editSpy).toHaveBeenCalledWith([7, 2], { allowOverlay: false, data: true, kind: "boolean", readonly: false, }); }); test("Emits activated event on Space key", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { key: " ", }); vi.runAllTimers(); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1]); }); test("keyDown and keyUp events include the cell location", async () => { let keyDownEvent: GridKeyEventArgs | undefined; let keyUpEvent: GridKeyEventArgs | undefined; const keyDown = (e: GridKeyEventArgs) => { keyDownEvent = e; }; const keyUp = (e: GridKeyEventArgs) => { keyUpEvent = e; }; vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { key: " ", }); fireEvent.keyUp(canvas, { key: " ", }); vi.runAllTimers(); expect(keyDownEvent?.location).toEqual([1, 1]); expect(keyUpEvent?.location).toEqual([1, 1]); }); test("Doesn't emit cell click if mouseDown happened in a different cell", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 300, // Col B, ends at x = 310 clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.mouseUp(canvas, { clientX: 320, // Col C, started at x = 310 clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).not.toHaveBeenCalled(); }); test("Doesn't emit header click if mouseDown happened in a different cell", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 300, // Col B, ends at x = 310 clientY: 16, // Header }); fireEvent.mouseUp(canvas, { clientX: 320, // Col C, started at x = 310 clientY: 16, // Header }); expect(spy).not.toHaveBeenCalled(); }); test("Uneven rows cell click", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( (r % 2 === 0 ? 32 : 64)} />, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 64 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith([1, 1], expect.anything()); }); test("Emits finished editing", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { keyCode: 74, key: "j", }); await new Promise(r => window.setTimeout(r, 1000)); const overlay = screen.getByDisplayValue("j"); vi.useFakeTimers(); fireEvent.keyDown(overlay, { key: "Enter", }); act(() => { vi.runAllTimers(); }); expect(spy).toBeCalledWith( { allowOverlay: true, allowWrapping: true, data: "j", displayData: "1, 1", kind: "text" }, [0, 1] ); }); test("Does not edit when validation fails", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( false} />, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { keyCode: 74, key: "j", }); await new Promise(r => window.setTimeout(r, 1000)); const overlay = screen.getByDisplayValue("j"); vi.useFakeTimers(); fireEvent.keyDown(overlay, { key: "Enter", }); act(() => { vi.runAllTimers(); }); expect(spy).not.toBeCalled(); }); test("Emits header click", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 16, // Header }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(1, expect.anything()); }); test("Emits header click on touch", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendTouchClick(canvas, { touches: [ { clientX: 300, // Col B clientY: 16, // Header }, ], }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(1, expect.anything()); }); test("Does emit header click on row marker column", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 10, // Col B clientY: 16, // Header }); expect(spy).not.toHaveBeenCalled(); }); test("Group header sections", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( ({ name: g, icon: "headerCode", })} columns={basicProps.columns.map(c => ({ ...c, group: "A" }))} onGridSelectionChange={spy} />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 16, // GroupHeader }); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.fromSingleSelection([0, 11]), rows: CompactSelection.empty(), }); spy.mockClear(); sendClick(canvas, { ctrlKey: true, clientX: 300, // Col B clientY: 16, // GroupHeader }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), }); spy.mockClear(); sendClick(canvas, { ctrlKey: true, clientX: 300, // Col B clientY: 16, // GroupHeader }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ rows: CompactSelection.empty(), current: undefined, columns: CompactSelection.fromSingleSelection([0, 11]), }); }); test("Rename group header shows", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( ({ ...c, group: c.title }))} onGroupHeaderRenamed={spy} />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 16, // Group Header }); await new Promise(r => window.setTimeout(r, 100)); sendClick(canvas, { clientX: 300, // Col B clientY: 16, // Group Header }); expect(spy).not.toHaveBeenCalled(); const groupInput = screen.getByTestId("group-rename-input"); expect(document.body.contains(groupInput)).toBe(true); fireEvent.change(groupInput, { target: { value: "Test", }, }); fireEvent.keyDown(groupInput, { key: "Enter", }); expect(spy).toHaveBeenCalledWith("B", "Test"); }); test("Emits header menu click", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( ({ ...c, hasMenu: true }))} onHeaderMenuClick={spy} />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 16, // Header }); await new Promise(r => window.setTimeout(r, 100)); sendClick(canvas, { clientX: 300, // Col B clientY: 16, // Header }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(1, expect.anything()); }); test("Emits group header clicked on touch", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( ({ ...c, group: "Main" }))} rowMarkers="both" onGroupHeaderClicked={spy} />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendTouchClick(canvas, { touches: [ { clientX: 300, // Col B clientY: 16, // Group header }, ], }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(1, expect.objectContaining({ location: [2, -2] })); }); test("Emits item hover on correct location", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(expect.objectContaining({ location: [1, 1] })); }); test("Emits mouse move on correct location", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(expect.objectContaining({ location: [1, 1] })); }); test("Delete cell", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.keyDown(canvas, { key: "Delete", }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: { cell: [2, 2], range: { x: 2, y: 2, height: 1, width: 1, }, rangeStack: [], }, }); }); test("Delete cell callback result", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( sel} onCellEdited={spy} gridSelection={{ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: { cell: [2, 2], range: { x: 2, y: 2, height: 1, width: 1, }, rangeStack: [], }, }} rowMarkers="both" />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.keyDown(canvas, { key: "Delete", }); expect(spy).toHaveBeenCalledWith([2, 2], expect.anything()); }); test("Delete custom", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( ({ kind: GridCellKind.Custom, allowOverlay: true, copyData: "fake", data: "fake", })} customRenderers={[ { draw: () => undefined, isMatch: (c): c is CustomCell => c.kind === GridCellKind.Custom, kind: GridCellKind.Custom, onDelete: spy, }, ]} onDelete={sel => sel} gridSelection={{ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: { cell: [2, 2], range: { x: 2, y: 2, height: 1, width: 1, }, rangeStack: [], }, }} rowMarkers="both" />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.keyDown(canvas, { key: "Delete", }); expect(spy).toHaveBeenCalledWith({ allowOverlay: true, copyData: "fake", data: "fake", kind: "custom" }); }); test("Delete row", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.keyDown(canvas, { key: "Delete", }); expect(spy).toHaveBeenCalled(); }); test("Delete range", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.keyDown(canvas, { key: "Delete", }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: { cell: [2, 2], range: { x: 2, y: 2, width: 4, height: 10 }, rangeStack: [], }, }); }); test("Open and close overlay", async () => { vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); await new Promise(r => window.setTimeout(r, 1000)); const overlay = screen.getByDisplayValue("Data: 1, 1"); expect(document.body.contains(overlay)).toBe(true); vi.useFakeTimers(); fireEvent.keyDown(canvas, { key: "Escape", }); act(() => { vi.runAllTimers(); }); expect(document.body.contains(overlay)).toBe(false); }); test("Open markdown overlay", async () => { vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 980, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); sendClick(canvas, { clientX: 980, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); const overlay = screen.getByText("Header: 9, 1"); expect(document.body.contains(overlay)).toBe(true); vi.useFakeTimers(); fireEvent.keyDown(canvas, { key: "Escape", }); act(() => { vi.runAllTimers(); }); expect(document.body.contains(overlay)).toBe(false); }); test("Open overlay with keypress", async () => { vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { keyCode: 74, key: "j", }); fireEvent.keyUp(canvas, { keyCode: 74, key: "j", }); const overlay = screen.getByDisplayValue("j"); expect(document.body.contains(overlay)).toBe(true); vi.useFakeTimers(); fireEvent.keyDown(overlay, { key: "Escape", }); act(() => { vi.runAllTimers(); }); expect(document.body.contains(overlay)).toBe(false); }); test("Open overlay with keypress when prior is disabled", async () => { vi.useFakeTimers(); render( { const r = basicProps.getCellContent(cell); if (cell[0] === 1 && cell[1] === 0) return { ...r, allowOverlay: false, readonly: true, }; return r; }} />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { keyCode: 74, key: "j", }); fireEvent.keyUp(canvas, { keyCode: 74, key: "j", }); const overlay = screen.getByDisplayValue("j"); expect(document.body.contains(overlay)).toBe(true); vi.useFakeTimers(); fireEvent.keyDown(overlay, { key: "Escape", }); act(() => { vi.runAllTimers(); }); expect(document.body.contains(overlay)).toBe(false); }); test("Send edit", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); act(() => { vi.runAllTimers(); }); fireEvent.keyDown(canvas, { keyCode: 74, key: "j", }); fireEvent.keyUp(canvas, { keyCode: 74, key: "j", }); act(() => { vi.runAllTimers(); }); const overlay = screen.getByDisplayValue("j"); expect(document.body.contains(overlay)).toBe(true); fireEvent.keyDown(overlay, { key: "Enter", }); act(() => { vi.runAllTimers(); }); expect(spy).toBeCalledWith([1, 1], expect.objectContaining({ data: "j" })); expect(document.body.contains(overlay)).toBe(false); }); test("Send edit with click off", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); act(() => { vi.runAllTimers(); }); fireEvent.keyDown(canvas, { keyCode: 74, key: "j", }); fireEvent.keyUp(canvas, { keyCode: 74, key: "j", }); act(() => { vi.runAllTimers(); }); const overlay = screen.getByDisplayValue("j"); expect(document.body.contains(overlay)).toBe(true); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 5 + 16, // Row 1 (0 indexed) }); act(() => { vi.runAllTimers(); }); expect(spy).toBeCalledWith([1, 1], expect.objectContaining({ data: "j" })); expect(document.body.contains(overlay)).toBe(false); }); test("Send edit with touch off", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); act(() => { vi.runAllTimers(); }); fireEvent.keyDown(canvas, { keyCode: 74, key: "j", }); fireEvent.keyUp(canvas, { keyCode: 74, key: "j", }); act(() => { vi.runAllTimers(); }); const overlay = screen.getByDisplayValue("j"); expect(document.body.contains(overlay)).toBe(true); sendTouchClick(canvas, { touches: [ { clientX: 300, // Col B clientY: 36 + 32 * 5 + 16, // Row 1 (0 indexed)} }, ], }); act(() => { vi.runAllTimers(); }); expect(spy).toBeCalledWith([1, 1], expect.objectContaining({ data: "j" })); expect(document.body.contains(overlay)).toBe(false); }); test("Directly toggle booleans", async () => { const spy = vi.fn(); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); // We need to be focused on the grid for booleans to toggle automatically act(() => { ref.current?.focus(); }); act(() => { vi.runAllTimers(); }); vi.useRealTimers(); // [7, 0] is a checked boolean const [checkedX, checkedY] = getCellCenterPositionForDefaultGrid([7, 0]); sendClick(canvas, { clientX: checkedX, clientY: checkedY }); expect(spy).toBeCalledWith([7, 0], expect.objectContaining({ data: false })); // [7, 1] is an unchecked boolean const [uncheckedX, uncheckedY] = getCellCenterPositionForDefaultGrid([7, 1]); sendClick(canvas, { clientX: uncheckedX, clientY: uncheckedY }); expect(spy).toBeCalledWith([7, 1], expect.objectContaining({ data: true })); // [7, 2] is an indeterminate boolean const [indeterminateX, indeterminateY] = getCellCenterPositionForDefaultGrid([7, 2]); sendClick(canvas, { clientX: indeterminateX, clientY: indeterminateY }); expect(spy).toBeCalledWith([7, 2], expect.objectContaining({ data: true })); // [7, 3] is an empty boolean const [emptyX, emptyY] = getCellCenterPositionForDefaultGrid([7, 3]); sendClick(canvas, { clientX: emptyX, clientY: emptyY }); expect(spy).toBeCalledWith([7, 3], expect.objectContaining({ data: true })); }); test("Directly toggle readonly booleans", async () => { const spy = vi.fn(); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); // We need to be focused on the grid for booleans to toggle automatically act(() => { ref.current?.focus(); }); act(() => { vi.runAllTimers(); }); vi.useRealTimers(); // [7, 0] is a checked boolean readonly const [checkedX, checkedY] = getCellCenterPositionForDefaultGrid([7, 5]); sendClick(canvas, { clientX: checkedX, clientY: checkedY }); expect(spy).not.toBeCalled(); }); test("Toggle readonly boolean with space", async () => { const spy = vi.fn(); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); // We need to be focused on the grid for booleans to toggle automatically act(() => { ref.current?.focus(); }); act(() => { vi.runAllTimers(); }); // [7, 0] is a checked boolean readonly const [checkedX, checkedY] = getCellCenterPositionForDefaultGrid([7, 5]); sendClick(canvas, { clientX: checkedX + 20, clientY: checkedY }); act(() => { vi.runAllTimers(); }); fireEvent.keyDown(canvas, { key: " ", }); expect(spy).not.toBeCalled(); }); test("Ref getBounds", async () => { const spy = vi.fn(); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); act(() => { vi.runAllTimers(); }); const bounds = ref.current?.getBounds(4, 4); expect(bounds).toEqual({ height: 33, width: 41, x: 696, y: 164, }); }); test("Ref getBounds entire grid", async () => { const spy = vi.fn(); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); const scroller = prep(false); assert(scroller !== null); act(() => { vi.runAllTimers(); }); vi.spyOn(scroller, "scrollWidth", "get").mockImplementation(() => 1000); vi.spyOn(scroller, "scrollHeight", "get").mockImplementation(() => 1000); const bounds = ref.current?.getBounds(); expect(bounds).toEqual({ height: 1000, width: 1000, x: 0, y: 0, }); }); test("Ctrl+Home", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "Home", ctrlKey: true, }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [0, 0] }) })); }); test("Ctrl+End", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "End", ctrlKey: true, }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [10, 1000] }) })); }); test("Ctrl+Shift+Home", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "Home", ctrlKey: true, shiftKey: true, }); expect(spy).toBeCalledWith( expect.objectContaining({ columns: CompactSelection.empty(), current: { cell: [1, 1], range: { height: 2, width: 2, x: 0, y: 0, }, rangeStack: [], }, rows: CompactSelection.empty(), }) ); }); test("Ctrl+Shift+End", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "End", ctrlKey: true, shiftKey: true, }); expect(spy).toBeCalledWith( expect.objectContaining({ columns: CompactSelection.empty(), current: { cell: [1, 1], range: { height: 999, width: 10, x: 1, y: 1, }, rangeStack: [], }, rows: CompactSelection.empty(), }) ); }); test("Page down", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "PageDown", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [1, 29] }) })); }); test("Page up", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "PageUp", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [1, 0] }) })); }); test("Arrow left", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowLeft", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [0, 1] }) })); }); test("Arrow shift left", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { shiftKey: true, key: "ArrowLeft", }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [1, 1], range: { x: 0, y: 1, width: 2, height: 1 } }), }) ); }); test("Arrow right", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowRight", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [2, 1] }) })); }); test("Arrow shift right", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { shiftKey: true, key: "ArrowRight", }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [1, 1], range: { x: 1, y: 1, width: 2, height: 1 } }), }) ); }); test("Tab navigation", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "Tab", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [2, 1] }) })); spy.mockClear(); fireEvent.keyDown(canvas, { key: "Tab", shiftKey: true, }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [1, 1] }) })); }); test("Arrow down", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowDown", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [1, 2] }) })); }); test("Arrow up", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowUp", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [1, 1] }) })); }); test("Freeze area reported", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); expect(spy).toBeCalledWith( expect.objectContaining({ height: 32, width: 8, x: 3, y: 0, }), 0, 0, expect.objectContaining({ freezeRegion: { height: 32, width: 3, x: 0, y: 0, }, freezeRegions: [ { height: 32, width: 3, x: 0, y: 0, }, { height: 2, width: 8, x: 3, y: 998, }, { height: 2, width: 3, x: 0, y: 998, }, ], selected: undefined, }) ); }); test("Search close", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const searchClose = screen.getByTestId("search-close-button"); fireEvent.click(searchClose); act(() => { vi.runAllTimers(); }); expect(spy).toBeCalled(); }); test("Trigger search results", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); vi.useFakeTimers(); const searchInput = screen.getByTestId("search-input"); fireEvent.change(searchInput, { target: { value: "1, 2", }, }); act(() => { vi.advanceTimersByTime(1000); vi.runAllTimers(); }); const searchResult = screen.getByTestId("search-result-area"); expect(searchResult.textContent).toBe("111 results"); fireEvent.keyDown(searchInput, { key: "Enter", }); fireEvent.keyDown(searchInput, { shiftKey: true, key: "Enter", }); fireEvent.keyDown(searchInput, { key: "Escape", }); act(() => { vi.runAllTimers(); }); expect(spy).toHaveBeenCalled(); }); test("Copy/paste", async () => { const spy = vi.fn(); const pasteSpy = vi.fn((_target: any, _values: any) => true); vi.useFakeTimers(); render( pasteSpy(...args)} />, { wrapper: Context, } ); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); act(() => { vi.runAllTimers(); }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowRight", shiftKey: true, }); act(() => { vi.runAllTimers(); }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [1, 2], range: { x: 1, y: 2, width: 2, height: 1 } }), }) ); fireEvent.copy(window); act(() => { vi.runAllTimers(); }); expect(navigator.clipboard.writeText).toBeCalledWith("1, 2\t2, 2"); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowDown", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [1, 3] }) })); fireEvent.paste(window); act(() => { vi.runAllTimers(); }); vi.useRealTimers(); await new Promise(r => window.setTimeout(r, 10)); expect(pasteSpy).toBeCalledWith( [1, 3], [ ["Sunday", "Dogs", "https://google.com"], ["Monday", "Cats", "https://google.com"], ["Tuesday", "Turtles", "https://google.com"], ["Wednesday", "Bears", "https://google.com"], ["Thursday", "L ions", "https://google.com"], ["Friday", "Pigs", "https://google.com"], [ "Saturday", 'Turkeys and some "quotes" and\na new line char "more quotes" plus a tab .', "https://google.com", ], ] ); }); test("Paste out of range does not crash", async () => { vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); act(() => { vi.runAllTimers(); }); fireEvent.keyDown(canvas, { key: "ArrowRight", ctrlKey: true, }); act(() => { vi.runAllTimers(); }); fireEvent.paste(window); act(() => { vi.runAllTimers(); }); vi.useRealTimers(); await new Promise(r => window.setTimeout(r, 10)); }); test("Cut cell", async () => { const spy = vi.fn(); const editSpy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); act(() => { vi.runAllTimers(); }); fireEvent.keyDown(canvas, { key: "ArrowRight", shiftKey: true, }); act(() => { vi.runAllTimers(); }); fireEvent.cut(window); vi.useRealTimers(); await new Promise(r => window.setTimeout(r, 10)); expect(navigator.clipboard.writeText).toBeCalledWith("1, 2\t2, 2"); expect(editSpy).toHaveBeenCalledWith([ { location: [1, 2], value: expect.objectContaining({ data: "" }), }, { location: [2, 2], value: expect.objectContaining({ data: "" }), }, ]); }); test("Paste custom cell does not crash", async () => { vi.useFakeTimers(); // eslint-disable-next-line unicorn/consistent-function-scoping const alwaysCustomCell = (_cell: Item): GridCell => { return { kind: GridCellKind.Custom, allowOverlay: true, data: "custom-cell-data", copyData: "custom-cell-copy-data", }; }; const spy = vi.fn(); render( true, onPaste: spy, isMatch: (_cell: CustomCell): _cell is CustomCell => true, }, ]} />, { wrapper: Context, } ); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); act(() => { vi.runAllTimers(); }); fireEvent.paste(window); act(() => { vi.runAllTimers(); }); vi.useRealTimers(); await new Promise(r => window.setTimeout(r, 10)); expect(spy).toBeCalledWith(expect.anything(), "custom-cell-data"); }); test("CustomCell onClick", async () => { vi.useFakeTimers(); const onClickSpy = vi.fn(); // eslint-disable-next-line unicorn/consistent-function-scoping, sonarjs/no-identical-functions const alwaysCustomCell = (_cell: Item): GridCell => { return { kind: GridCellKind.Custom, allowOverlay: true, data: "custom-cell-data", copyData: "custom-cell-copy-data", }; }; render( true, onClick: onClickSpy, isMatch: (_cell: CustomCell): _cell is CustomCell => true, }, ]} />, { wrapper: Context, } ); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); // mouse down col b row 2 fireEvent.mouseDown(canvas, { clientX: 300, clientY: 36 + 32 * 2 + 16, }); // mouse move col b row 3 fireEvent.mouseMove(canvas, { clientX: 300, clientY: 36 + 32 * 2 + 16, buttons: 1, }); // mouse up fireEvent.mouseUp(canvas, { clientX: 300, clientY: 36 + 32 * 2 + 16, }); act(() => { vi.runAllTimers(); }); expect(onClickSpy).toBeCalled(); }); test("CustomCell onClick fires with same restriction as onCellClicked", async () => { vi.useFakeTimers(); const onClickSpy = vi.fn(); // eslint-disable-next-line unicorn/consistent-function-scoping, sonarjs/no-identical-functions const alwaysCustomCell = (_cell: Item): GridCell => { return { kind: GridCellKind.Custom, allowOverlay: true, data: "custom-cell-data", copyData: "custom-cell-copy-data", }; }; render( true, onClick: onClickSpy, isMatch: (_cell: CustomCell): _cell is CustomCell => true, }, ]} />, { wrapper: Context, } ); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); // mouse down col b row 2 fireEvent.mouseDown(canvas, { clientX: 300, clientY: 36 + 32 * 2 + 16, }); // mouse move col b row 3 fireEvent.mouseMove(canvas, { clientX: 300, clientY: 36 + 32 * 3 + 16, buttons: 1, }); // mouse up fireEvent.mouseUp(canvas, { clientX: 300, clientY: 36 + 32 * 3 + 16, }); act(() => { vi.runAllTimers(); }); expect(onClickSpy).not.toBeCalled(); }); test("onCellsEdited blocks onCellEdited", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( true} />, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); act(() => { vi.runAllTimers(); }); fireEvent.paste(window); act(() => { vi.runAllTimers(); }); vi.useRealTimers(); await new Promise(r => window.setTimeout(r, 10)); expect(spy).not.toBeCalled(); }); test("Copy/paste with simple getCellsForSelection", async () => { const spy = vi.fn(); const pasteSpy = vi.fn((_target: any, _values: any) => true); vi.useFakeTimers(); render( pasteSpy(...args)} />, { wrapper: Context, } ); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); act(() => { vi.runAllTimers(); }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowRight", shiftKey: true, }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [1, 2], range: { x: 1, y: 2, width: 2, height: 1 } }), }) ); fireEvent.copy(window); act(() => { vi.runAllTimers(); }); expect(navigator.clipboard.writeText).toBeCalledWith("1, 2\t2, 2"); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowDown", }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [1, 3] }) })); fireEvent.paste(window); act(() => { vi.runAllTimers(); }); vi.useRealTimers(); await new Promise(resolve => setTimeout(resolve, 10)); expect(pasteSpy).toBeCalledWith( [1, 3], [ ["Sunday", "Dogs", "https://google.com"], ["Monday", "Cats", "https://google.com"], ["Tuesday", "Turtles", "https://google.com"], ["Wednesday", "Bears", "https://google.com"], ["Thursday", "L ions", "https://google.com"], ["Friday", "Pigs", "https://google.com"], [ "Saturday", 'Turkeys and some "quotes" and\na new line char "more quotes" plus a tab .', "https://google.com", ], ] ); }); test("Copy rows", async () => { vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); fireEvent.copy(window); await new Promise(resolve => setTimeout(resolve, 10)); expect(navigator.clipboard.writeText).toBeCalledWith( "Data: 0, 3\t1, 3\t2, 3\t3\tFoobar\t************\tFoobar\t\tשלום 8, 3\t# Header: 9, 3\thttps://example.com/10/3" ); }); test("Copy cols", async () => { vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); vi.spyOn(document, "activeElement", "get").mockImplementation(() => canvas); fireEvent.copy(window); await new Promise(resolve => setTimeout(resolve, 10)); expect(navigator.clipboard.writeText).toBeCalled(); }); test("Hover header does not fetch invalid cell", async () => { const spy = vi.fn(basicProps.getCellContent); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); spy.mockClear(); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 16, // Header }); expect(spy).not.toHaveBeenCalled(); }); test("Blit does not crash vertical scroll", async () => { vi.useFakeTimers(); render(, { wrapper: Context, }); const scroller = prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 16, // Header }); await new Promise(resolve => setTimeout(resolve, 100)); if (scroller !== null) { vi.spyOn(scroller, "scrollWidth", "get").mockImplementation(() => basicProps.columns.map(c => (isSizedGridColumn(c) ? c.width : 150)).reduce((pv, cv) => pv + cv, 0) ); vi.spyOn(scroller, "scrollHeight", "get").mockImplementation(() => 1000 * 32 + 36); vi.spyOn(scroller, "scrollLeft", "get").mockImplementation(() => 0); vi.spyOn(scroller, "scrollTop", "get").mockImplementation(() => 55); fireEvent.scroll(scroller); } await new Promise(resolve => setTimeout(resolve, 100)); if (scroller !== null) { vi.spyOn(scroller, "scrollWidth", "get").mockImplementation(() => basicProps.columns.map(c => (isSizedGridColumn(c) ? c.width : 150)).reduce((pv, cv) => pv + cv, 0) ); vi.spyOn(scroller, "scrollHeight", "get").mockImplementation(() => 1000 * 32 + 36); vi.spyOn(scroller, "scrollLeft", "get").mockImplementation(() => 0); vi.spyOn(scroller, "scrollTop", "get").mockImplementation(() => 0); fireEvent.scroll(scroller); } await new Promise(resolve => setTimeout(resolve, 100)); expect(document.body.contains(canvas)).toBe(true); }); test("Blit does not crash horizontal scroll", async () => { vi.useFakeTimers(); render( , { wrapper: Context, } ); const scroller = prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 16, // Header }); await new Promise(resolve => setTimeout(resolve, 100)); if (scroller !== null) { vi.spyOn(scroller, "scrollWidth", "get").mockImplementation(() => basicProps.columns.map(c => (isSizedGridColumn(c) ? c.width : 150)).reduce((pv, cv) => pv + cv, 0) ); vi.spyOn(scroller, "scrollHeight", "get").mockImplementation(() => 1000 * 32 + 36); vi.spyOn(scroller, "scrollLeft", "get").mockImplementation(() => 55); vi.spyOn(scroller, "scrollTop", "get").mockImplementation(() => 0); fireEvent.scroll(scroller); } await new Promise(resolve => setTimeout(resolve, 100)); if (scroller !== null) { vi.spyOn(scroller, "scrollWidth", "get").mockImplementation(() => basicProps.columns.map(c => (isSizedGridColumn(c) ? c.width : 150)).reduce((pv, cv) => pv + cv, 0) ); vi.spyOn(scroller, "scrollHeight", "get").mockImplementation(() => 1000 * 32 + 36); vi.spyOn(scroller, "scrollLeft", "get").mockImplementation(() => 0); vi.spyOn(scroller, "scrollTop", "get").mockImplementation(() => 0); fireEvent.scroll(scroller); } await new Promise(resolve => setTimeout(resolve, 100)); expect(document.body.contains(canvas)).toBe(true); }); test("New row", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); vi.useFakeTimers(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 990, // Trailing row }); expect(spy).toHaveBeenCalled(); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).toHaveBeenCalled(); }); test("Click row marker", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); vi.useFakeTimers(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 10, // Row marker clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection(2), current: undefined, }); }); test("Shift click row marker", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 10, // Row marker clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); sendClick(canvas, { shiftKey: true, clientX: 10, // Row marker clientY: 36 + 32 * 5 + 16, // Row 2 (0 indexed) }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection([2, 6]), current: undefined, }); }); test("Drag click row marker", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 10, // Row marker clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); fireEvent.mouseMove(canvas, { shiftKey: true, clientX: 10, // Row marker clientY: 36 + 32 * 5 + 16, // Row 2 (0 indexed) buttons: 1, }); fireEvent.mouseUp(canvas, { shiftKey: true, clientX: 10, // Row marker clientY: 36 + 32 * 5 + 16, // Row 2 (0 indexed) }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection([2, 6]), current: undefined, }); }); test("Shift click row marker - no multi-select", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 10, // Row marker clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); sendClick(canvas, { shiftKey: true, clientX: 10, // Row marker clientY: 36 + 32 * 5 + 16, // Row 2 (0 indexed) }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection(5), current: undefined, }); }); test("Ctrl click row marker", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 10, // Row marker clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); sendClick(canvas, { ctrlKey: true, clientX: 10, // Row marker clientY: 36 + 32 * 5 + 16, // Row 2 (0 indexed) }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection(2).add(5), current: undefined, }); spy.mockClear(); sendClick(canvas, { ctrlKey: true, clientX: 10, // Row marker clientY: 36 + 32 * 5 + 16, // Row 2 (0 indexed) }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection(2), current: undefined, }); }); test("Ctrl click row marker - no multi", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 10, // Row marker clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); sendClick(canvas, { ctrlKey: true, clientX: 10, // Row marker clientY: 36 + 32 * 5 + 16, // Row 2 (0 indexed) }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection(5), current: undefined, }); spy.mockClear(); sendClick(canvas, { ctrlKey: true, clientX: 10, // Row marker clientY: 36 + 32 * 5 + 16, // Row 2 (0 indexed) }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: undefined, }); }); test("Shift click grid selection", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); sendClick(canvas, { shiftKey: true, clientX: 400, // Col C clientY: 36 + 32 * 6 + 16, // Row 6 (0 indexed) }); expect(spy).toHaveBeenCalledWith( expect.objectContaining({ current: { cell: [1, 2], range: { x: 1, y: 2, width: 2, height: 5, }, rangeStack: [], }, }) ); }); test("Fill down", async () => { const spy = vi.fn(); const multiSpy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); sendClick(canvas, { shiftKey: true, clientX: 400, // Col C clientY: 36 + 32 * 6 + 16, // Row 6 (0 indexed) }); fireEvent.keyDown(canvas, { keyCode: 68, ctrlKey: true, }); expect(spy).toHaveBeenCalledTimes(8); expect(multiSpy).toHaveBeenCalled(); }); test("Fill right", async () => { const spy = vi.fn(); const multiSpy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); sendClick(canvas, { shiftKey: true, clientX: 400, // Col C clientY: 36 + 32 * 6 + 16, // Row 6 (0 indexed) }); fireEvent.keyDown(canvas, { keyCode: 82, ctrlKey: true, }); expect(spy).toHaveBeenCalledTimes(5); expect(multiSpy).toHaveBeenCalled(); }); test("Clear selection", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); sendClick(canvas, { shiftKey: true, clientX: 400, // Col C clientY: 36 + 32 * 6 + 16, // Row 6 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "Escape", }); expect(spy).toBeCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: undefined, }); }); test("Delete range", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); sendClick(canvas, { shiftKey: true, clientX: 400, // Col C clientY: 36 + 32 * 6 + 16, // Row 6 (0 indexed) }); fireEvent.keyDown(canvas, { key: "Delete", }); expect(spy).toBeCalledTimes(10); }); test("Click out of bounds", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 100, // Col A clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); sendClick(canvas, { shiftKey: true, clientX: 200, // Col B clientY: 36 + 32 * 6 + 16, // Row 6 (0 indexed) }); spy.mockClear(); sendClick(canvas, { shiftKey: true, clientX: 700, // OOB clientY: 36 + 32 * 6 + 16, // Row 6 (0 indexed) }); expect(spy).toBeCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: undefined, }); }); test("Delete Column", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 16, // Header }); fireEvent.keyDown(canvas, { key: "Delete", }); expect(spy).toBeCalledTimes(1000); }); test("DND Columns", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 300, // Col B clientY: 16, // Header }); fireEvent.mouseMove(canvas, { clientX: 250, clientY: 16, buttons: 1, }); fireEvent.mouseMove(canvas, { clientX: 200, clientY: 16, buttons: 1, }); fireEvent.mouseMove(canvas, { clientX: 150, clientY: 16, buttons: 1, }); fireEvent.mouseMove(canvas, { clientX: 100, clientY: 16, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 100, // Col A clientY: 16, // Header }); fireEvent.click(canvas, { clientX: 100, // Col A clientY: 16, // Header }); expect(spy).toBeCalledWith(1, 0); }); test("Resize Column", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 310, // Col B Right Edge clientY: 16, // Header }); fireEvent.mouseMove(canvas, { clientX: 350, clientY: 16, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 350, clientY: 16, }); fireEvent.click(canvas, { clientX: 350, clientY: 16, }); expect(spy).toBeCalledWith({ icon: "headerCode", title: "B", width: 160 }, 200, 1, 200); }); test("Auto Resize Column", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 310, // Col B Right Edge clientY: 16, // Header }); fireEvent.mouseUp(canvas, { clientX: 310, clientY: 16, }); fireEvent.mouseDown(canvas, { clientX: 310, // Col B Right Edge clientY: 16, // Header }); fireEvent.mouseUp(canvas, { clientX: 310, clientY: 16, }); fireEvent.click(canvas, { clientX: 310, clientY: 16, }); expect(spy).toBeCalledWith({ icon: "headerCode", title: "B", width: 160 }, 50, 1, 50); }); test("Auto Resize Column Ref", async () => { const spy = vi.fn(); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(); ref.current?.remeasureColumns(CompactSelection.fromSingleSelection(1)); expect(spy).toBeCalledWith({ icon: "headerCode", title: "B", width: 160 }, 50, 1, 50); }); test("Resize Column End Called", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 310, // Col B Right Edge clientY: 16, // Header }); fireEvent.mouseMove(canvas, { clientX: 350, clientY: 16, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 350, clientY: 16, }); fireEvent.click(canvas, { clientX: 350, clientY: 16, }); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toBeCalledWith({ icon: "headerCode", title: "B", width: 160 }, 200, 1, 200); }); test("Resize column end called correct number of times", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 310, // Col B Right Edge clientY: 16, // Header }); fireEvent.mouseMove(canvas, { clientX: 350, clientY: 16, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 350, clientY: 16, }); fireEvent.click(canvas, { clientX: 350, clientY: 16, }); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toBeCalledWith({ icon: "headerCode", title: "B", width: 160 }, 200, 1, 200); }); test("Resize Multiple Column", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 310, // Col B Right Edge clientY: 16, // Header }); fireEvent.mouseMove(canvas, { clientX: 350, clientY: 16, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 350, clientY: 16, }); fireEvent.click(canvas, { clientX: 350, clientY: 16, }); expect(spy).toBeCalledTimes(5); }); test("Resize Last Column", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 314, // Col B Right Edge clientY: 16, // Header }); fireEvent.mouseMove(canvas, { clientX: 350, clientY: 16, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 350, clientY: 16, }); fireEvent.click(canvas, { clientX: 350, clientY: 16, }); expect(spy).toBeCalledWith({ icon: "headerCode", title: "B", width: 160 }, 200, 1, 200); }); test("Drag reorder row", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 10, // Col B Right Edge clientY: 300, // Header }); fireEvent.mouseMove(canvas, { clientX: 10, clientY: 400, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 10, clientY: 400, }); fireEvent.click(canvas, { clientX: 10, clientY: 400, }); expect(spy).toBeCalledWith(8, 11); }); test("Select range with mouse", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 }); spy.mockClear(); fireEvent.mouseMove(canvas, { clientX: 600, // Col B clientY: 36 + 32 * 12 + 16, // Row 2 buttons: 1, }); expect(spy).toBeCalledWith( expect.objectContaining({ current: { cell: [1, 2], range: { height: 11, width: 3, x: 1, y: 2 }, rangeStack: [] }, }) ); fireEvent.mouseUp(canvas, { clientX: 600, // Col B clientY: 36 + 32 * 12 + 16, // Row 2 }); }); test("Select range with mouse middle click fails", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { button: 0, clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 }); fireEvent.mouseDown(canvas, { button: 1, clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 }); spy.mockClear(); fireEvent.mouseMove(canvas, { clientX: 600, // Col B clientY: 36 + 32 * 12 + 16, // Row 2 buttons: 1, }); expect(spy).not.toBeCalled(); fireEvent.mouseUp(canvas, { clientX: 600, // Col B clientY: 36 + 32 * 12 + 16, // Row 2 }); }); test("Select all", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 10, clientY: 10, }); expect(spy).toBeCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection([0, 1000]), current: undefined, }); sendClick(canvas, { clientX: 10, clientY: 10, }); expect(spy).toBeCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: undefined, }); }); test("Draggable", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( { spy(e); e.setData("text/plain", "payload"); }} isDraggable={true} />, { wrapper: Context, } ); const scroller = prep(); // const canvas = screen.getByTestId("data-grid-canvas"); if (scroller !== null) { const mockDownEv = createEvent.mouseDown(scroller); fireEvent(scroller, mockDownEv); expect(mockDownEv.defaultPrevented).toBe(false); const mockEv = createEvent.dragStart(scroller); Object.assign(mockEv, { clientX: 100, clientY: 100, dataTransfer: { setData: () => undefined, setDragImage: () => undefined, effectAllowed: null, }, }); fireEvent(scroller, mockEv); } expect(spy).toHaveBeenCalled(); }); test("Click cell does not double-emit selectedrows/columns", async () => { const gridSelectionSpy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); expect(gridSelectionSpy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [1, 2], range: { height: 1, width: 1, x: 1, y: 2 } }), }) ); gridSelectionSpy.mockClear(); fireEvent.keyDown(canvas, { key: "Escape", }); expect(gridSelectionSpy).toBeCalledWith({ rows: CompactSelection.empty(), columns: CompactSelection.empty(), }); }); test("Span expansion", async () => { const spy = vi.fn(); vi.useFakeTimers(); const getCellContent: (typeof basicProps)["getCellContent"] = c => { const [col, row] = c; if (row === 3 && col >= 2 && col <= 3) { return { ...basicProps.getCellContent([2, 3]), span: [2, 3] as const, }; } return basicProps.getCellContent(c); }; render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 350, // Col C clientY: 36 + 32 * 2 + 16, // Row 2 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { shiftKey: true, key: "ArrowDown", }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [2, 2], range: { x: 2, y: 2, width: 2, height: 2 } }), }) ); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowDown", }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [2, 3], range: { x: 2, y: 3, width: 2, height: 1 } }), }) ); }); test("Imperative Handle works", async () => { const spy = vi.fn(); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(); act(() => { void ref.current?.emit("delete"); void ref.current?.emit("fill-right"); void ref.current?.emit("fill-down"); void ref.current?.emit("copy"); void ref.current?.emit("paste"); ref.current?.scrollTo(5, 10); ref.current?.updateCells([{ cell: [0, 0] }]); }); }); test("Imperative scrollTo false fire", async () => { vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); act(() => { ref.current?.scrollTo(5, 10); }); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).not.toBeCalled(); }); test("Imperative scrollTo cell", async () => { vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); act(() => { ref.current?.scrollTo(5, 500); }); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).toBeCalledWith(0, 15_101); }); test("Imperative scrollTo pixel", async () => { vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); act(() => { ref.current?.scrollTo(5, { amount: 1500, unit: "px", }); }); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).toBeCalledWith(0, 533); }); test("Imperative scrollTo pixel start", async () => { vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); act(() => { ref.current?.scrollTo( 5, { amount: 1500, unit: "px", }, undefined, undefined, undefined, { vAlign: "start", } ); }); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).toBeCalledWith(0, 1464); }); test("Imperative scrollTo pixel center", async () => { vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); act(() => { ref.current?.scrollTo( 5, { amount: 1500, unit: "px", }, undefined, undefined, undefined, { vAlign: "center", } ); }); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).toBeCalledWith(0, 998.5); }); test("Imperative scrollTo pixel end", async () => { vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); act(() => { ref.current?.scrollTo( 5, { amount: 1500, unit: "px", }, undefined, undefined, undefined, { vAlign: "end", } ); }); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).toBeCalledWith(0, 533); }); test("Imperative damage gets right cell", async () => { const spy = vi.fn(basicProps.getCellContent); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(); spy.mockClear(); act(() => { ref.current?.updateCells([{ cell: [1, 0] }]); }); expect(spy).toBeCalledWith([1, 0]); }); test("On-scroll does not spuriously fire on select", async () => { const spy = vi.fn(basicProps.getCellContent); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 965, }); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).not.toBeCalled(); }); test("Keyboard scroll with controlled selection does not double fire", async () => { const spy = vi.fn(basicProps.getCellContent); vi.useFakeTimers(); const ref = React.createRef(); render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 965, }); act(() => { vi.runAllTimers(); }); // make sure we clear the mock in case a spurios scroll was emitted (test above) (Element.prototype.scrollTo as Mock).mockClear(); fireEvent.keyDown(canvas, { key: "ArrowDown" }); fireEvent.keyUp(canvas, { key: "ArrowDown" }); act(() => { vi.runAllTimers(); }); expect(Element.prototype.scrollTo).toBeCalledTimes(1); }); test("Ctrl Arrow keys", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowDown", ctrlKey: true, }); const cols = basicProps.columns.length; expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [1, 999] }) })); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowRight", ctrlKey: true, }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [cols - 1, 999] }) }) ); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowUp", ctrlKey: true, }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [cols - 1, 0] }) }) ); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowLeft", ctrlKey: true, }); expect(spy).toBeCalledWith(expect.objectContaining({ current: expect.objectContaining({ cell: [0, 0] }) })); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowDown", ctrlKey: true, shiftKey: true, }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [0, 0], range: { x: 0, y: 0, width: 1, height: 1000 } }), }) ); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowRight", ctrlKey: true, shiftKey: true, }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [0, 0], range: { x: 0, y: 0, width: cols, height: 1000 } }), }) ); }); test("Select range with mouse going out of bounds", async () => { const spy = vi.fn(); vi.useFakeTimers(); const columns = basicProps.columns.slice(0, 2); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 }); spy.mockClear(); fireEvent.mouseMove(canvas, { clientX: 600, // Col B clientY: 36 + 32 * 12 + 16, // Row 2 buttons: 1, }); expect(spy).toBeCalledWith( expect.objectContaining({ current: expect.objectContaining({ cell: [1, 2], range: { height: 11, width: 1, x: 1, y: 2 } }), }) ); fireEvent.mouseUp(canvas, { clientX: 600, // Col B clientY: 36 + 32 * 12 + 16, // Row 2 }); }); test("Select all keybind", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.keyDown(canvas, { key: "a", keyCode: 65, ctrlKey: true, }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: { cell: [0, 0], range: { x: 0, y: 0, width: 11, height: 1000, }, rangeStack: [], }, }); }); test("Select column with blending", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: " ", ctrlKey: true, }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.fromSingleSelection(1), rows: CompactSelection.empty(), current: { cell: [1, 1], range: { x: 1, y: 1, width: 1, height: 1, }, rangeStack: [], }, }); }); test("Select column", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: " ", ctrlKey: true, }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.fromSingleSelection(1), rows: CompactSelection.empty(), current: undefined, }); }); test("Select row with blending", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: " ", shiftKey: true, }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection(1), current: { cell: [1, 1], range: { x: 1, y: 1, width: 1, height: 1, }, rangeStack: [], }, }); }); test("Select row", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); spy.mockClear(); fireEvent.keyDown(canvas, { key: " ", shiftKey: true, }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection(1), current: undefined, }); }); test("Select range with mouse then permissive move", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 300, // Col B clientY: 36 + 32 * 2 + 16, // Row 2 }); fireEvent.mouseMove(canvas, { clientX: 600, // Col B clientY: 36 + 32 * 12 + 16, // Row 2 buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 600, // Col B clientY: 36 + 32 * 12 + 16, // Row 2 }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "ArrowLeft", altKey: true, }); expect(spy).toBeCalledWith( expect.objectContaining({ current: { cell: [0, 2], range: { height: 1, width: 1, x: 0, y: 2 }, rangeStack: [{ height: 11, width: 3, x: 1, y: 2 }], }, }) ); }); test("Does not emits header menu click when move", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( ({ ...c, hasMenu: true }))} onHeaderMenuClick={spy} />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 16 + 200, // Not Header }); await new Promise(r => window.setTimeout(r, 100)); fireEvent.mouseDown(canvas, { clientX: 300, // Col B clientY: 16 + 200, // Not Header }); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 16, // Header buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 300, // Col B clientY: 16, // Header }); expect(spy).not.toHaveBeenCalled(); }); test("Dragging header disables vertical autoscroll", async () => { const spy = Element.prototype.scrollBy as Mock; spy.mockClear(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); fireEvent.mouseDown(canvas, { clientX: 300, // Col B clientY: 16, // Header }); fireEvent.mouseMove(canvas, { clientX: 300, // Col B clientY: 0, buttons: 1, }); await new Promise(r => window.setTimeout(r, 100)); fireEvent.mouseUp(canvas, { clientX: 300, // Col B clientY: 0, }); expect(spy).not.toHaveBeenCalled(); }); test("Use fill handle", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 290, // Col A clientY: 36 + 30, // Row 2 }); fireEvent.mouseDown(canvas, { clientX: 308, // Col A clientY: 36 + 30, // Row 2 }); fireEvent.mouseMove(canvas, { clientX: 308, // Col A clientY: 36 + 32 * 2 + 16, // Row 2 buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 308, // Col A clientY: 36 + 32 * 2 + 16, // Row 2 }); fireEvent.click(canvas, { clientX: 308, // Col A clientY: 36 + 32 * 2 + 16, // Row 2 }); expect(spy).toBeCalledTimes(2); }); test("Use fill handle diagonal", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 290, // Col A clientY: 36 + 30, // Row 2 }); fireEvent.mouseDown(canvas, { clientX: 308, // Col A clientY: 36 + 30, // Row 2 }); fireEvent.mouseMove(canvas, { clientX: 360, clientY: 36 + 32 * 5 + 16, // Row 5 buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 360, clientY: 36 + 32 * 5 + 16, // Row 5 }); fireEvent.click(canvas, { clientX: 360, clientY: 36 + 32 * 5 + 16, // Row 5 }); expect(spy).toBeCalledTimes(5); }); test("onFillPattern", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 290, // Col A clientY: 36 + 30, // Row 2 }); fireEvent.mouseDown(canvas, { clientX: 308, // Col A clientY: 36 + 30, // Row 2 }); fireEvent.mouseMove(canvas, { clientX: 360, clientY: 36 + 32 * 5 + 16, // Row 5 buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 360, clientY: 36 + 32 * 5 + 16, // Row 5 }); fireEvent.click(canvas, { clientX: 360, clientY: 36 + 32 * 5 + 16, // Row 5 }); expect(spy).toBeCalledTimes(1); }); test("Use fill handle into blank", async () => { const spy = vi.fn(); vi.useFakeTimers(); render(, { wrapper: Context, }); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 290, clientY: 36 + 30, }); fireEvent.mouseDown(canvas, { clientX: 308, clientY: 36 + 30, }); fireEvent.mouseMove(canvas, { clientX: 308, clientY: 36 + 32 * 5 + 16, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 308, clientY: 36 + 32 * 5 + 16, }); fireEvent.click(canvas, { clientX: 308, clientY: 36 + 32 * 5 + 16, }); expect(spy).toBeCalledTimes(2); }); test("Use fill handle into trailing row", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( undefined} trailingRowOptions={{ sticky: true, }} />, { wrapper: Context, } ); prep(); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 290, clientY: 36 + 30, }); fireEvent.mouseDown(canvas, { clientX: 308, clientY: 36 + 30, }); fireEvent.mouseMove(canvas, { clientX: 308, clientY: 800, buttons: 1, }); fireEvent.mouseMove(canvas, { clientX: 308, clientY: 995, buttons: 1, }); fireEvent.mouseUp(canvas, { clientX: 308, clientY: 995, }); fireEvent.click(canvas, { clientX: 308, clientY: 995, }); expect(spy).toBeCalledTimes(2); }); test("Close overlay with enter key", async () => { const spy = vi.fn(); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { key: "Enter", }); vi.runAllTimers(); spy.mockClear(); fireEvent.keyDown(canvas, { key: "Enter", }); expect(spy).toHaveBeenCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: { cell: [1, 2], range: { x: 1, y: 2, width: 1, height: 1, }, rangeStack: [], }, }); }); test("Clear selection when suddenly out of range", async () => { const spy = vi.fn(); vi.useFakeTimers(); const { rerender } = render(, { wrapper: Context, }); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, clientY: 36 + 32 * 5 + 16, }); vi.runAllTimers(); spy.mockClear(); rerender(); vi.runAllTimers(); expect(spy).toBeCalledWith({ columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: undefined, }); }); test("Enter key does not trigger disallowed row fetch", async () => { const spy = vi.fn(basicProps.getCellContent); vi.useFakeTimers(); render( , { wrapper: Context, } ); prep(false); const canvas = screen.getByTestId("data-grid-canvas"); sendClick(canvas, { clientX: 300, // Col B clientY: 36 + 32 + 16, // Row 1 (0 indexed) }); fireEvent.keyDown(canvas, { key: "Enter", }); spy.mockClear(); fireEvent.keyDown(canvas, { key: "Enter", }); vi.runAllTimers(); expect(spy.mock.calls.findIndex(x => x[0][1] > 1)).toBe(-1); }); });