/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/components/widgets/DataFrame/columns/MultiselectColumn.test.ts
286 строк
9 KB
Maya Barnes
Consolidate/refactor theme color mapping (#13740)
31 янв 2026, 01:03
Не верифицирован
31 янв 2026, 01:03
b411eaa
Код
Авторство
О чём код?
/** * 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 { GridCellKind } from "@glideapps/glide-data-grid" import { MultiSelectCellType } from "@glideapps/glide-data-grid-cells" import { Field, List, Utf8 } from "apache-arrow" import { transparentize } from "color2k" import { DataFrameCellType } from "~lib/dataframes/arrowTypeUtils" import { mockTheme } from "~lib/mocks/mockTheme" import { blend, getThemeBackgroundColors } from "~lib/theme" import MultiselectColumn, { type MultiselectColumnParams, prepareOptions, } from "./MultiselectColumn" import { BaseColumnProps, isErrorCell, isMissingValueCell } from "./utils" const MOCK_MULTISELECT_COLUMN_PROPS: BaseColumnProps = { id: "1", name: "multiselect_column", title: "Multi-select column", indexNumber: 0, isEditable: true, isHidden: false, isIndex: false, isPinned: false, isStretched: false, arrowType: { type: DataFrameCellType.DATA, arrowField: new Field( "multiselect_column", new List(new Field("item", new Utf8(), true)), true ), pandasType: { field_name: "multiselect_column", name: "multiselect_column", pandas_type: "object", numpy_type: "list[unicode]", metadata: null, }, }, } function getMultiselectColumn( params?: MultiselectColumnParams, columnPropsOverwrite?: Partial<BaseColumnProps> ): ReturnType<typeof MultiselectColumn> { return MultiselectColumn( { ...MOCK_MULTISELECT_COLUMN_PROPS, ...columnPropsOverwrite, columnTypeOptions: params, } as BaseColumnProps, mockTheme.emotion ) } describe("MultiselectColumn", () => { it("creates a valid column instance with string options", () => { const options = ["foo", "bar"] const mockColumn = getMultiselectColumn({ options }) expect(mockColumn.kind).toEqual("multiselect") expect(mockColumn.title).toEqual(MOCK_MULTISELECT_COLUMN_PROPS.title) expect(mockColumn.id).toEqual(MOCK_MULTISELECT_COLUMN_PROPS.id) expect(mockColumn.sortMode).toEqual("default") const mockCell = mockColumn.getCell(["foo"]) expect(mockCell.kind).toEqual(GridCellKind.Custom) expect(mockColumn.getCellValue(mockCell)).toEqual(["foo"]) // returns values const expectedOptions = prepareOptions(options, mockTheme.emotion) expect((mockCell as MultiSelectCellType).data.options).toEqual( expectedOptions ) expect((mockCell as MultiSelectCellType).data.allowCreation).toEqual(false) expect((mockCell as MultiSelectCellType).data.allowDuplicates).toEqual( false ) }) it("uses faded style for index columns", () => { const mockColumn = getMultiselectColumn( { options: ["foo"] }, { isIndex: true } ) const mockCell = mockColumn.getCell(["foo"]) as MultiSelectCellType expect(mockCell.style).toEqual("faded") }) it("respects accept_new_options config for creation", () => { const mockColumn = getMultiselectColumn({ options: ["foo", "bar"], accept_new_options: true, }) const mockCell = mockColumn.getCell(["foo"]) as MultiSelectCellType expect(mockCell.data.allowCreation).toEqual(true) expect(mockCell.data.allowDuplicates).toEqual(false) }) it("validates values against options when accept_new_options is false", () => { const mockColumn = getMultiselectColumn({ options: ["A", "B"] }) const validatedCell = mockColumn.getCell(["A", "C"], true) expect(isErrorCell(validatedCell)).toEqual(false) expect((validatedCell as MultiSelectCellType).data.values).toEqual(["A"]) // filtered }) it("creates error cell if values are not in options when validating", () => { const mockColumn = getMultiselectColumn({ options: ["A", "B"] }) const errorCell = mockColumn.getCell(["Z"], true) expect(isErrorCell(errorCell)).toEqual(true) }) it.each([[null], [undefined]])( "%p is interpreted as missing value", // eslint-disable-next-line @typescript-eslint/no-explicit-any -- casting for test inputs only (input: any) => { const mockColumn = getMultiselectColumn({ options: ["foo", "bar"] }) const mockCell = mockColumn.getCell(input) expect(mockColumn.getCellValue(mockCell)).toEqual(null) expect(isMissingValueCell(mockCell)).toEqual(true) } ) it('"" (empty string) is interpreted as empty array, not missing', () => { const mockColumn = getMultiselectColumn({ options: ["foo", "bar"] }) const mockCell = mockColumn.getCell("") expect(mockColumn.getCellValue(mockCell)).toEqual([]) expect(isMissingValueCell(mockCell)).toEqual(false) }) it("marks non-string array values as non-editable error when editing", () => { const mockColumn = getMultiselectColumn( { options: ["x"] }, { isEditable: true, } ) const cell = mockColumn.getCell([1, "x"]) as MultiSelectCellType // Should mark as error and readonly due to non-string values expect((cell as unknown as { readonly?: boolean }).readonly).toEqual(true) expect(isErrorCell(cell)).toEqual(true) }) it.each([ [["a", "b"], "a,b"], [["a,b", "c"], "a b,c"], [[], ""], ])( "correctly prepares data for copy (%p -> %p)", (input: string[], expectedCopy: string) => { const mockColumn = getMultiselectColumn({ options: ["a", "b", "c"] }) const cell = mockColumn.getCell(input) as MultiSelectCellType expect(cell.copyData).toEqual(expectedCopy) } ) }) describe("prepareOptions", () => { it.each([ [ ["foo", "bar"], [ { value: "foo", label: undefined, color: undefined }, { value: "bar", label: undefined, color: undefined }, ], ], [ [ { value: "us", label: "United States" }, { value: "de", label: "Germany" }, ], [ { value: "us", label: "United States", color: undefined }, { value: "de", label: "Germany", color: undefined }, ], ], [ // Filters out empty string and null entries ["", "A", null, "B"] as unknown as (string | { value: string })[], [ { value: "A", label: undefined, color: undefined }, { value: "B", label: undefined, color: undefined }, ], ], [ // Trims whitespace around values [" foo ", "bar "], [ { value: "foo", label: undefined, color: undefined }, { value: "bar", label: undefined, color: undefined }, ], ], ])("normalizes %j into %j", (input, expected) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- casting for test inputs only expect(prepareOptions(input as any, mockTheme.emotion)).toEqual(expected) }) it("applies theme color mapping and blends for named colors", () => { const namedColorOption = [ { value: "prio", label: "Priority", color: "red" }, ] const opts = prepareOptions(namedColorOption, mockTheme.emotion) const mdColors = getThemeBackgroundColors(mockTheme.emotion) const expected = blend(mdColors.redbg, mockTheme.emotion.colors.bgColor) expect(opts[0].color).toEqual(expected) }) it("blends custom color with theme background when color is not mapped", () => { const customColor = "#008000" const opts = prepareOptions( [{ value: "ok", color: customColor }], mockTheme.emotion ) const expected = blend(customColor, mockTheme.emotion.colors.bgColor) expect(opts[0].color).toEqual(expected) }) it("assigns categorical chart colors for 'auto' and wraps around", () => { const categoricalColors = mockTheme.emotion.colors.chartCategoricalColors // Create more options than categorical colors to verify wrap-around const count = categoricalColors.length + 3 const input = Array.from({ length: count }, (_, i) => ({ value: `v${i}`, color: "auto" as const, })) const opts = prepareOptions(input, mockTheme.emotion) const expectedColors = input.map((_, i) => blend( transparentize(categoricalColors[i % categoricalColors.length], 0.7), mockTheme.emotion.colors.bgColor ) ) expect(opts.map(o => o.color)).toEqual(expectedColors) }) it("leaves string options colorless and assigns 'auto' for object options", () => { const categoricalColors = mockTheme.emotion.colors.chartCategoricalColors const input = [ "A", { value: "B", color: "auto" as const }, "C", { value: "D", color: "auto" as const }, ] const opts = prepareOptions(input, mockTheme.emotion) const expected = [ { value: "A", label: undefined, color: undefined }, { value: "B", label: undefined, color: blend( transparentize(categoricalColors[0], 0.7), mockTheme.emotion.colors.bgColor ), }, { value: "C", label: undefined, color: undefined }, { value: "D", label: undefined, color: blend( transparentize(categoricalColors[1], 0.7), mockTheme.emotion.colors.bgColor ), }, ] expect(opts).toEqual(expected) }) })