/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/hooks/useTextInputAutoExpand.test.ts
342 строки
11 KB
Ken McGrady
Update copyright header to 2026 (#13491)
02 янв 2026, 16:21
Не верифицирован
02 янв 2026, 16:21
374540a
Код
Авторство
О чём код?
/** * 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. */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { RefObject } from "react" import { act, renderHook } from "@testing-library/react" import { useTextInputAutoExpand } from "./useTextInputAutoExpand" // Mock the useTheme hook vi.mock("@emotion/react", () => ({ useTheme: () => ({ sizes: { minElementHeight: "2.5rem", }, }), keyframes: () => "keyframes", })) // Helper to create a mock textarea ref const createMockTextareaRef = ( overrides: Partial<{ offsetHeight: number scrollHeight: number style: { height: string } }> = {} ): RefObject<HTMLTextAreaElement> => { const defaultElement = { offsetHeight: 40, scrollHeight: 40, style: { height: "", }, } return { current: { ...defaultElement, ...overrides, } as HTMLTextAreaElement, } } describe("useTextInputAutoExpand", () => { describe("initialization", () => { it("should initialize with default values", () => { const mockTextareaRef = createMockTextareaRef() const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: mockTextareaRef }) ) expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") // theme.sizes.minElementHeight expect(result.current.maxHeight).toBe("260px") // 40 * 6.5 expect(typeof result.current.updateScrollHeight).toBe("function") }) it("should initialize with null ref", () => { const nullRef = { current: null } const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: nullRef }) ) expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") expect(result.current.maxHeight).toBe("") }) it("should calculate scroll height correctly", () => { const expandedRef = createMockTextareaRef({ offsetHeight: 40, scrollHeight: 80, // Greater than offsetHeight }) const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: expandedRef }) ) // Should be extended since scrollHeight (80) > minHeight (40) + rounding offset (1) // The hook should automatically calculate this on initialization expect(result.current.isExtended).toBe(true) expect(result.current.height).toBe("81px") // scrollHeight + ROUNDING_OFFSET }) it("should handle textarea within rounding offset", () => { const smallDifferenceRef = createMockTextareaRef({ offsetHeight: 40, scrollHeight: 40.5, // Within rounding offset of 1 }) const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: smallDifferenceRef }) ) // Should not be extended due to rounding offset // The hook should automatically calculate this on initialization expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") }) }) describe("scroll height calculation", () => { it("should update isExtended when textarea scroll height changes", () => { // Start with a non-extended textarea const dynamicRef = createMockTextareaRef() const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: dynamicRef }) ) // Initial state should not be extended expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") // Simulate content growth by changing scrollHeight Object.defineProperty(dynamicRef.current!, "scrollHeight", { value: 100, // Now greater than offsetHeight writable: true, configurable: true, }) // Trigger recalculation act(() => { result.current.updateScrollHeight() }) // Should now be extended expect(result.current.isExtended).toBe(true) expect(result.current.height).toBe("101px") // new scrollHeight + ROUNDING_OFFSET }) it("should become extended when scroll height decreases significantly below offset height", () => { // Start with a non-extended textarea (equal heights) const shrinkingRef = createMockTextareaRef() const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: shrinkingRef }) ) // Initial state should not be extended expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") // Simulate content shrinking by changing scrollHeight to smaller value Object.defineProperty(shrinkingRef.current!, "scrollHeight", { value: 30, // Now less than offsetHeight (40) writable: true, configurable: true, }) // Trigger recalculation act(() => { result.current.updateScrollHeight() }) // Should be extended because Math.abs(30-40) = 10 > ROUNDING_OFFSET // The current hook logic treats significant differences in either direction as extended expect(result.current.isExtended).toBe(true) expect(result.current.height).toBe("31px") // scrollHeight + ROUNDING_OFFSET }) it("should update isExtended when textarea shrinks back to normal", () => { // Start with an extended textarea const shrinkingRef = createMockTextareaRef({ offsetHeight: 40, scrollHeight: 100, // Greater than offsetHeight - extended }) const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: shrinkingRef }) ) // Initial state should be extended expect(result.current.isExtended).toBe(true) expect(result.current.height).toBe("101px") // Simulate content shrinking by changing scrollHeight back to normal Object.defineProperty(shrinkingRef.current!, "scrollHeight", { value: 40, // Same as offsetHeight - not extended writable: true, configurable: true, }) // Trigger recalculation act(() => { result.current.updateScrollHeight() }) // Should no longer be extended expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") // Back to default height }) }) describe("dependencies", () => { it("should update scroll height when dependencies change", () => { const reactiveRef = createMockTextareaRef() let dependency = "initial" const { result, rerender } = renderHook(() => useTextInputAutoExpand({ textareaRef: reactiveRef, dependencies: [dependency], }) ) // Initial state should not be extended expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") // Simulate content growth by changing scrollHeight Object.defineProperty(reactiveRef.current!, "scrollHeight", { value: 80, // Now significantly greater than offsetHeight writable: true, configurable: true, }) // Change dependency and rerender - this should trigger updateScrollHeight dependency = "changed" rerender() // Should now be extended due to dependency change triggering recalculation expect(result.current.isExtended).toBe(true) expect(result.current.height).toBe("81px") // new scrollHeight + ROUNDING_OFFSET }) it("should handle multiple dependencies", () => { const multiDepRef = createMockTextareaRef() let dep1 = "a" const dep2 = "b" const { result, rerender } = renderHook(() => useTextInputAutoExpand({ textareaRef: multiDepRef, dependencies: [dep1, dep2], }) ) // Initial state should not be extended expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") // Simulate content growth by changing scrollHeight Object.defineProperty(multiDepRef.current!, "scrollHeight", { value: 100, // Now significantly greater than offsetHeight writable: true, configurable: true, }) // Change one dependency and rerender - this should trigger updateScrollHeight dep1 = "changed1" rerender() // Should now be extended due to dependencies change triggering recalculation expect(result.current.isExtended).toBe(true) expect(result.current.height).toBe("101px") // new scrollHeight + ROUNDING_OFFSET }) }) describe("updateScrollHeight function", () => { it("should handle null textarea ref in updateScrollHeight", () => { const nullRef = { current: null } const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: nullRef }) ) // Should not throw error act(() => { result.current.updateScrollHeight() }) expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") }) }) describe("style height manipulation", () => { it("should temporarily reset height to auto during calculation", () => { const mockTextareaRef = createMockTextareaRef() const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: mockTextareaRef }) ) act(() => { result.current.updateScrollHeight() }) // After calculation, height should be reset to empty string expect(mockTextareaRef.current?.style.height).toBe("") }) }) describe("edge cases", () => { it("should handle zero scroll height", () => { const zeroScrollRef = createMockTextareaRef({ offsetHeight: 40, scrollHeight: 0, }) const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: zeroScrollRef }) ) // The hook should automatically handle this on initialization expect(result.current.isExtended).toBe(false) expect(result.current.height).toBe("2.5rem") }) it("should handle very large content", () => { const largeContentRef = createMockTextareaRef({ offsetHeight: 40, scrollHeight: 1000, }) const { result } = renderHook(() => useTextInputAutoExpand({ textareaRef: largeContentRef }) ) // The hook should automatically handle this on initialization expect(result.current.isExtended).toBe(true) expect(result.current.height).toBe("1001px") expect(result.current.maxHeight).toBe("260px") // Still limited by max height }) }) })