/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/components/widgets/DataFrame/hooks/useSelectionHandler.test.ts
649 строк
20 KB
Lukas Masuch
Split `Arrow.proto` into `Table.proto` and `Dataframe.proto` (#13768)
04 фев 2026, 22:22
Не верифицирован
04 фев 2026, 22:22
dc9d8fe
Код
Авторство
О чём код?
/** * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { CompactSelection } from "@glideapps/glide-data-grid" import { act, renderHook } from "@testing-library/react" import { Field, Utf8 } from "apache-arrow" import { Dataframe as DataframeProto } from "@streamlit/protobuf" import { TextColumn } from "~lib/components/widgets/DataFrame/columns" import { DataFrameCellType } from "~lib/dataframes/arrowTypeUtils" import useSelectionHandler from "./useSelectionHandler" const syncSelectionStateMock = vi.fn() describe("useSelectionHandler hook", () => { beforeEach(() => { vi.clearAllMocks() }) it("detects single row selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [DataframeProto.SelectionMode.SINGLE_ROW], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(true) expect(result.current.isMultiRowSelectionActivated).toEqual(false) expect(result.current.isColumnSelectionActivated).toEqual(false) expect(result.current.isMultiColumnSelectionActivated).toEqual(false) }) it("detects multi row selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [DataframeProto.SelectionMode.MULTI_ROW], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(true) expect(result.current.isMultiRowSelectionActivated).toEqual(true) expect(result.current.isColumnSelectionActivated).toEqual(false) expect(result.current.isMultiColumnSelectionActivated).toEqual(false) }) it("detects single column selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [DataframeProto.SelectionMode.SINGLE_COLUMN], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(false) expect(result.current.isMultiRowSelectionActivated).toEqual(false) expect(result.current.isColumnSelectionActivated).toEqual(true) expect(result.current.isMultiColumnSelectionActivated).toEqual(false) }) it("detects multi column selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [DataframeProto.SelectionMode.MULTI_COLUMN], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(false) expect(result.current.isMultiRowSelectionActivated).toEqual(false) expect(result.current.isColumnSelectionActivated).toEqual(true) expect(result.current.isMultiColumnSelectionActivated).toEqual(true) }) it("detects single cell selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [DataframeProto.SelectionMode.SINGLE_CELL], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(false) expect(result.current.isColumnSelectionActivated).toEqual(false) expect(result.current.isCellSelectionActivated).toEqual(true) expect(result.current.isMultiCellSelectionActivated).toEqual(false) }) it("detects multi cell selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [DataframeProto.SelectionMode.MULTI_CELL], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(false) expect(result.current.isColumnSelectionActivated).toEqual(false) expect(result.current.isCellSelectionActivated).toEqual(true) expect(result.current.isMultiCellSelectionActivated).toEqual(true) }) it("detects mixed multi selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(true) expect(result.current.isMultiRowSelectionActivated).toEqual(true) expect(result.current.isColumnSelectionActivated).toEqual(true) expect(result.current.isMultiColumnSelectionActivated).toEqual(true) }) it("disables all selections if empty table", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), true, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(false) expect(result.current.isMultiRowSelectionActivated).toEqual(false) expect(result.current.isColumnSelectionActivated).toEqual(false) expect(result.current.isMultiColumnSelectionActivated).toEqual(false) }) it("disables all selections if table is disabled", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, true, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(false) expect(result.current.isMultiRowSelectionActivated).toEqual(false) expect(result.current.isColumnSelectionActivated).toEqual(false) expect(result.current.isMultiColumnSelectionActivated).toEqual(false) expect(result.current.isCellSelectionActivated).toEqual(false) expect(result.current.isMultiCellSelectionActivated).toEqual(false) }) it("correctly processes and clears column selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(true) expect(result.current.isMultiRowSelectionActivated).toEqual(true) expect(result.current.isColumnSelectionActivated).toEqual(true) expect(result.current.isMultiColumnSelectionActivated).toEqual(true) // Process a new selection with a single column selected: const newGridSelection = { columns: CompactSelection.fromSingleSelection(0), rows: CompactSelection.empty(), current: undefined, } act(() => { const { processSelectionChange } = result.current processSelectionChange?.(newGridSelection) }) // Check that it detects a column to be selected: expect(result.current.isColumnSelected).toEqual(true) expect(result.current.isRowSelected).toEqual(false) expect(result.current.isCellSelected).toEqual(false) expect(result.current.gridSelection).toEqual(newGridSelection) expect(syncSelectionStateMock).toBeCalledTimes(1) // Check that the selection can also be cleared again: act(() => { const { clearSelection } = result.current clearSelection?.() }) // Check that it clears the selection: expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.isCellSelected).toEqual(false) expect(syncSelectionStateMock).toBeCalledTimes(2) }) it("correctly processes and clears row selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(true) expect(result.current.isMultiRowSelectionActivated).toEqual(true) expect(result.current.isColumnSelectionActivated).toEqual(true) expect(result.current.isMultiColumnSelectionActivated).toEqual(true) // Process a new selection with a single row selected: const newGridSelection = { columns: CompactSelection.empty(), rows: CompactSelection.fromSingleSelection(0), current: undefined, } act(() => { const { processSelectionChange } = result.current processSelectionChange?.(newGridSelection) }) // Check that it detects a row to be selected: expect(result.current.isRowSelected).toEqual(true) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.isCellSelected).toEqual(false) expect(result.current.gridSelection).toEqual(newGridSelection) expect(syncSelectionStateMock).toBeCalledTimes(1) // Check that the selection can also be cleared again: act(() => { const { clearSelection } = result.current clearSelection?.() }) // Check that it clears the selection: expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.isCellSelected).toEqual(false) expect(syncSelectionStateMock).toBeCalledTimes(2) }) it("correctly processes and clears row+column selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, false, [], syncSelectionStateMock ) ) expect(result.current.isRowSelectionActivated).toEqual(true) expect(result.current.isMultiRowSelectionActivated).toEqual(true) expect(result.current.isColumnSelectionActivated).toEqual(true) expect(result.current.isMultiColumnSelectionActivated).toEqual(true) const newGridSelection = { columns: CompactSelection.fromSingleSelection(0), rows: CompactSelection.fromSingleSelection(0), current: undefined, } // Process a new selection with a row and column: act(() => { const { processSelectionChange } = result.current processSelectionChange?.(newGridSelection) }) // Check that it detects a row+column to be selected: expect(result.current.isRowSelected).toEqual(true) expect(result.current.isColumnSelected).toEqual(true) expect(result.current.isCellSelected).toEqual(false) expect(result.current.gridSelection).toEqual(newGridSelection) expect(syncSelectionStateMock).toBeCalledTimes(1) // Check that the clear cell selections doesn't clear the row+column selection: act(() => { const { clearSelection } = result.current // Keep column & row selections: clearSelection?.(true, true) }) expect(result.current.isRowSelected).toEqual(true) expect(result.current.isColumnSelected).toEqual(true) expect(result.current.isCellSelected).toEqual(false) // This should not call syncSelectionState callback: expect(syncSelectionStateMock).toBeCalledTimes(1) // Check that the selection can also be cleared again: act(() => { const { clearSelection } = result.current clearSelection?.() }) // Check that it clears the selection: expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.isCellSelected).toEqual(false) expect(syncSelectionStateMock).toBeCalledTimes(2) }) it("clearSelection keeps only columns and syncs", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, false, [], syncSelectionStateMock ) ) const newGridSelection = { columns: CompactSelection.fromSingleSelection(0), rows: CompactSelection.fromSingleSelection(0), current: undefined, } act(() => { const { processSelectionChange } = result.current processSelectionChange?.(newGridSelection) }) expect(result.current.isRowSelected).toEqual(true) expect(result.current.isColumnSelected).toEqual(true) expect(syncSelectionStateMock).toBeCalledTimes(1) act(() => { const { clearSelection } = result.current // Clear rows, keep columns clearSelection?.(false, true) }) expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(true) expect(result.current.isCellSelected).toEqual(false) expect(syncSelectionStateMock).toBeCalledTimes(2) }) it("clearSelection keeps only rows and syncs", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, false, [], syncSelectionStateMock ) ) const newGridSelection = { columns: CompactSelection.fromSingleSelection(0), rows: CompactSelection.fromSingleSelection(0), current: undefined, } act(() => { const { processSelectionChange } = result.current processSelectionChange?.(newGridSelection) }) expect(result.current.isRowSelected).toEqual(true) expect(result.current.isColumnSelected).toEqual(true) expect(syncSelectionStateMock).toBeCalledTimes(1) act(() => { const { clearSelection } = result.current // Keep rows, clear columns clearSelection?.(true, false) }) expect(result.current.isRowSelected).toEqual(true) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.isCellSelected).toEqual(false) expect(syncSelectionStateMock).toBeCalledTimes(2) }) it("correctly processes and clears cell selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, false, [], syncSelectionStateMock ) ) const newGridSelection = { columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: { cell: [0, 0], }, } // Process a new cell selection: act(() => { const { processSelectionChange } = result.current // @ts-expect-error processSelectionChange?.(newGridSelection) }) // Check that it detects a row+column to be selected: expect(result.current.isCellSelected).toEqual(true) expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.gridSelection).toEqual(newGridSelection) expect(syncSelectionStateMock).not.toBeCalled() // Check that the clear cell selections doesn't clear the row+column selection: act(() => { const { clearSelection } = result.current clearSelection?.(true, true) }) expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.isCellSelected).toEqual(false) // This should not call syncSelectionState callback: expect(syncSelectionStateMock).not.toBeCalled() }) it("correctly processes and clears cell selection when cell selection is activated", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [DataframeProto.SelectionMode.MULTI_CELL], }), false, false, [], syncSelectionStateMock ) ) const newGridSelection = { columns: CompactSelection.empty(), rows: CompactSelection.empty(), current: { cell: [0, 0], }, } // Process a new cell selection: act(() => { const { processSelectionChange } = result.current // @ts-expect-error processSelectionChange?.(newGridSelection) }) expect(result.current.isCellSelected).toEqual(true) expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.gridSelection).toEqual(newGridSelection) expect(syncSelectionStateMock).toBeCalledTimes(1) expect(syncSelectionStateMock).toHaveBeenLastCalledWith( expect.anything(), true ) // Clear the selection completely: act(() => { const { clearSelection } = result.current clearSelection?.() }) expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(false) expect(result.current.isCellSelected).toEqual(false) expect(syncSelectionStateMock).toBeCalledTimes(2) expect(syncSelectionStateMock).toHaveBeenLastCalledWith( expect.anything(), true ) }) it("ignores index column selection", () => { const { result } = renderHook(() => useSelectionHandler( DataframeProto.create({ selectionMode: [ DataframeProto.SelectionMode.MULTI_ROW, DataframeProto.SelectionMode.MULTI_COLUMN, ], }), false, false, [ // Configure 1 index column TextColumn({ arrowType: { type: DataFrameCellType.DATA, arrowField: new Field("index-0", new Utf8(), true), pandasType: { field_name: "index-0", name: "index-0", pandas_type: "unicode", numpy_type: "unicode", metadata: null, }, }, id: "index-0", name: "", indexNumber: 0, isEditable: true, isHidden: false, isIndex: true, isPinned: false, isStretched: false, title: "", }), ], syncSelectionStateMock ) ) // Select the index column: const firstGridSelection = { columns: CompactSelection.fromSingleSelection(0), rows: CompactSelection.empty(), cell: undefined, } act(() => { const { processSelectionChange } = result.current processSelectionChange?.(firstGridSelection) }) // Nothing should have been selected since the index column is ignored: expect(result.current.isCellSelected).toEqual(false) expect(result.current.isRowSelected).toEqual(false) expect(result.current.isColumnSelected).toEqual(false) }) })