/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/app/src/components/StreamlitDialog/SettingsDialog.test.tsx
341 строка
10 KB
Maya Barnes
Refactor `SettingsDialog` & add copyable version (#13791)
04 фев 2026, 01:32
Не верифицирован
04 фев 2026, 01:32
4ef65df
Код
Авторство
О чём код?
/** * 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 { screen } from "@testing-library/react" import { PointerEventsCheckLevel, userEvent, } from "@testing-library/user-event" import { MetricsManager } from "@streamlit/app/src/MetricsManager" import { AUTO_THEME_NAME, createPresetThemes, CUSTOM_THEME_DARK_NAME, CUSTOM_THEME_LIGHT_NAME, CUSTOM_THEME_NAME, customTheme, darkTheme, lightTheme, mockSessionInfo, SessionInfo, ThemeConfig, ThemeContextProps, } from "@streamlit/lib" import { renderWithContexts } from "@streamlit/lib/testing" import { Props, SettingsDialog } from "./SettingsDialog" const mockSetTheme = vi.fn() const mockCopyToClipboard = vi.fn() vi.mock("~lib/hooks/useCopyToClipboard", () => ({ useCopyToClipboard: () => ({ isCopied: false, copyToClipboard: mockCopyToClipboard, label: "Copy to clipboard", }), })) export const autoCustomTheme: ThemeConfig = { name: "Use system setting", emotion: lightTheme.emotion, basewebTheme: lightTheme.basewebTheme, primitives: lightTheme.primitives, } const customThemeLight: ThemeConfig = { name: "Custom Theme Light", emotion: lightTheme.emotion, basewebTheme: lightTheme.basewebTheme, primitives: lightTheme.primitives, } const customThemeDark: ThemeConfig = { name: "Custom Theme Dark", emotion: darkTheme.emotion, basewebTheme: darkTheme.basewebTheme, primitives: darkTheme.primitives, } const getThemeContext = ( extend?: Partial<ThemeContextProps> ): Partial<ThemeContextProps> => ({ activeTheme: lightTheme, setTheme: mockSetTheme, availableThemes: [], ...extend, }) const getProps = (extend?: Partial<Props>): Props => ({ isServerConnected: true, onClose: vi.fn(), onSave: vi.fn(), settings: { wideMode: false, runOnSave: false }, allowRunOnSave: false, animateModal: true, metricsMgr: new MetricsManager(mockSessionInfo()), sessionInfo: mockSessionInfo(), ...extend, }) describe("SettingsDialog", () => { beforeEach(() => { vi.clearAllMocks() mockCopyToClipboard.mockClear() }) it("renders without crashing", () => { const availableThemes = [lightTheme, darkTheme] const props = getProps() const themeContext = getThemeContext({ availableThemes }) renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) expect(screen.getByText("Settings")).toBeVisible() }) it("should render run on save checkbox", async () => { const user = userEvent.setup() const props = getProps({ allowRunOnSave: true, }) const themeContext = getThemeContext() renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) await user.click(screen.getByText("Run on save")) expect(props.onSave).toHaveBeenCalledTimes(1) expect(props.onSave).toHaveBeenCalledWith( expect.objectContaining({ runOnSave: true }) ) }) it("should render wide mode checkbox", async () => { const user = userEvent.setup() const props = getProps() const themeContext = getThemeContext() renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) expect(screen.getByText("Wide mode")).toBeVisible() await user.click(screen.getByText("Wide mode")) expect(props.onSave).toHaveBeenCalledTimes(1) expect(props.onSave).toHaveBeenCalledWith( expect.objectContaining({ wideMode: true }) ) }) it("should render theme selector", () => { const availableThemes = [lightTheme, darkTheme] const props = getProps() const themeContext = getThemeContext({ availableThemes }) renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) expect(screen.getByText("Choose app theme")).toBeVisible() expect(screen.getByRole("combobox")).toBeVisible() }) it("if single custom theme exists, only show Custom Theme as option & disable selectbox", () => { // When single custom theme exists (no light/dark versions), this is the only option // and the preset themes are removed from available themes const availableThemes = [customTheme] const props = getProps() const themeContext = getThemeContext({ availableThemes, activeTheme: customTheme, }) renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) const selectbox = screen.getByRole("combobox") expect(selectbox).toBeVisible() expect(selectbox).toBeDisabled() expect(screen.getByText(CUSTOM_THEME_NAME)).toBeVisible() }) it("if Custom Theme Light active, show correct active theme & light/dark/auto custom themes as options", async () => { const user = userEvent.setup() // When custom theme light & dark exist, also have auto theme // and the preset themes are removed from available themes const availableThemes = [ autoCustomTheme, customThemeLight, customThemeDark, ] const props = getProps() const themeContext = getThemeContext({ availableThemes, activeTheme: customThemeLight, }) renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) // Correct selected theme is shown expect(screen.getByText(CUSTOM_THEME_LIGHT_NAME)).toBeVisible() const selectbox = screen.getByRole("combobox") await user.click(selectbox) // Should only show Auto (Use System Setting), Custom Theme Light and Custom Theme Dark as options const options = screen.getAllByRole("option") expect(options).toHaveLength(3) expect(options[0]).toHaveTextContent(AUTO_THEME_NAME) expect(options[1]).toHaveTextContent(CUSTOM_THEME_LIGHT_NAME) expect(options[2]).toHaveTextContent(CUSTOM_THEME_DARK_NAME) }) it("if Custom Theme Dark active, show correct active theme & light/dark/auto custom themes as options", async () => { const user = userEvent.setup() const availableThemes = [ autoCustomTheme, customThemeLight, customThemeDark, ] const props = getProps() const themeContext = getThemeContext({ availableThemes, activeTheme: customThemeDark, }) renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) // Correct selected theme is shown expect(screen.getByText(CUSTOM_THEME_DARK_NAME)).toBeVisible() const selectbox = screen.getByRole("combobox") await user.click(selectbox) // Should only show Auto (Use System Setting), Custom Theme Light and Custom Theme Dark as options const options = screen.getAllByRole("option") expect(options).toHaveLength(3) expect(options[0]).toHaveTextContent(AUTO_THEME_NAME) expect(options[1]).toHaveTextContent(CUSTOM_THEME_LIGHT_NAME) expect(options[2]).toHaveTextContent(CUSTOM_THEME_DARK_NAME) }) it("should not show custom theme as option if it does not exist", async () => { const user = userEvent.setup() const presetThemes = createPresetThemes() const availableThemes = [...presetThemes] const props = getProps() const themeContext = getThemeContext({ availableThemes }) renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) expect(screen.getByText("Light")).toBeVisible() await user.click(screen.getByRole("combobox")) expect(screen.getAllByRole("option")).toHaveLength(presetThemes.length) expect(screen.queryByText(CUSTOM_THEME_NAME)).not.toBeInTheDocument() }) it("shows the currently active theme as selected", async () => { const user = userEvent.setup() const props = getProps() const presetThemes = createPresetThemes() const availableThemes = [...presetThemes] const themeContext = getThemeContext({ activeTheme: darkTheme, availableThemes, }) renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) expect(screen.getByText("Dark")).toBeVisible() await user.click(screen.getByRole("combobox")) expect(screen.getAllByRole("option")).toHaveLength(presetThemes.length) }) it("shows version string if SessionInfo is initialized", () => { const props = getProps({ sessionInfo: mockSessionInfo({ streamlitVersion: "42.42.42" }), }) const themeContext = getThemeContext() renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) const versionRegex = /Made with Streamlit\s*42\.42\.42/ const versionText = screen.getByText(versionRegex) expect(versionText).toBeDefined() const copyButton = screen.getByRole("button", { name: "Copy version to clipboard", }) expect(copyButton).toHaveAttribute("title", "Copy version to clipboard") expect(screen.getByTestId("stVersionCopyButton")).toBeDefined() }) it("copies only the version when the copy button is clicked", async () => { // Ignore pointer-events since CSS hides/shows the copy button in JSDOM. const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never, }) const props = getProps({ sessionInfo: mockSessionInfo({ streamlitVersion: "1.77.2" }), }) const themeContext = getThemeContext() renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) await user.click( screen.getByRole("button", { name: "Copy version to clipboard" }) ) expect(mockCopyToClipboard).toHaveBeenCalledWith("1.77.2") }) it("shows no version string if SessionInfo is not initialized", () => { const sessionInfo = new SessionInfo() expect(sessionInfo.isSet).toBe(false) const props = getProps({ sessionInfo }) const themeContext = getThemeContext() renderWithContexts(<SettingsDialog {...props} />, { themeContext: themeContext, }) const versionRegex = /^Made with Streamlit.*/ const nonExistentText = screen.queryByText(versionRegex) expect(nonExistentText).not.toBeInTheDocument() }) })