Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add st.popover layout container #7908

Merged
merged 40 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
de403bc
Add initial version of popover container
LukasMasuch Jan 5, 2024
dcd4ed1
Improvements
LukasMasuch Jan 5, 2024
9ed53f5
Add margin bottom
LukasMasuch Jan 5, 2024
c583b5d
Merge remote-tracking branch 'remote/develop' into feature/popover-co…
LukasMasuch Jan 11, 2024
0232930
Update popover to allow to run tests
LukasMasuch Jan 11, 2024
4080266
Update tests
LukasMasuch Jan 11, 2024
df27239
Merge remote-tracking branch 'remote/develop' into feature/popover-co…
LukasMasuch Jan 29, 2024
1fd5cc0
Mini fix with popover
LukasMasuch Jan 29, 2024
df526e7
Add unit tests
LukasMasuch Jan 31, 2024
276f43f
Add unit test
LukasMasuch Jan 31, 2024
8cb5907
Apply some updates
LukasMasuch Jan 31, 2024
6f4857c
Update unit test
LukasMasuch Jan 31, 2024
e6d873f
Add dynamic arrow
LukasMasuch Jan 31, 2024
1194dd0
Finalize e2e tests
LukasMasuch Jan 31, 2024
c3bafca
Update docstring
LukasMasuch Jan 31, 2024
ab88a0b
Update snapshots
LukasMasuch Jan 31, 2024
85e9952
Apply PR feedback
LukasMasuch Jan 31, 2024
94f6bcf
Add a couple more comments
LukasMasuch Jan 31, 2024
deea0e8
Update docstring.
LukasMasuch Jan 31, 2024
94445a3
Update popover test
LukasMasuch Jan 31, 2024
6568984
Remove screenshots
LukasMasuch Jan 31, 2024
8f7932e
Update tests
LukasMasuch Jan 31, 2024
d64b202
Add snapshots
LukasMasuch Jan 31, 2024
743b76d
Add help parameter for popover
LukasMasuch Feb 1, 2024
2df5617
Merge remote-tracking branch 'remote/develop' into feature/popover-co…
LukasMasuch Feb 16, 2024
b01be23
Merge branch 'feature/help-for-popover' into feature/popover-container
LukasMasuch Feb 16, 2024
f53b330
Add support for disabled and help
LukasMasuch Feb 16, 2024
0b9a761
Fix popover container
LukasMasuch Feb 16, 2024
64aca77
Update props in test
LukasMasuch Feb 16, 2024
175db0b
Fix overflow tests
LukasMasuch Feb 17, 2024
e3d7ca2
Update e2e tests
LukasMasuch Feb 17, 2024
f866a04
Fix unit test
LukasMasuch Feb 17, 2024
d2b1b21
Update test
LukasMasuch Feb 17, 2024
93d057e
Add snapshot test
LukasMasuch Feb 17, 2024
962563d
Fix test
LukasMasuch Feb 17, 2024
ab0ba6c
Improve width behaviour
LukasMasuch Feb 18, 2024
789b49b
Fix typo in docstring
LukasMasuch Feb 18, 2024
214f00c
Fix test
LukasMasuch Feb 20, 2024
dfbf8df
Fix popover e2e test
LukasMasuch Feb 20, 2024
ccbbcba
Improve docstrings
LukasMasuch Feb 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 24 additions & 31 deletions e2e/specs/st_tooltips.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ describe("tooltips on widgets", () => {
});

it("displays tooltips on metric", () => {
cy.get(`[data-testid=stMetricLabel] .stTooltipIcon`).should("have.length", 1);
cy.get(`[data-testid=stMetricLabel] .stTooltipIcon`).should(
"have.length",
1
);
});
});

Expand All @@ -110,67 +113,55 @@ describe("tooltip text with dedent on widgets", () => {
});

it("Display text properly on tooltips on text input", () => {
cy.get(`.stTextInput .stTooltipIcon`)
.invoke("show")
.click();
cy.get("[data-testid=stMarkdownContainer]").should('contain', defaultTooltip);
cy.get(`.stTextInput .stTooltipIcon`).invoke("show").click();
cy.get("[data-testid=stMarkdownContainer]").should(
"contain",
defaultTooltip
);
});


it("Display text properly on tooltips on numberinput", () => {
cy.get(`.stNumberInput .stTooltipIcon`)
.invoke("show")
.click();
cy.get(`.stNumberInput .stTooltipIcon`).invoke("show").click();
cy.get("[data-testid=stMarkdownContainer] .stCodeBlock").should(
"contain",
tooltipCodeBlock1
);
});

it("Display text properly on tooltips on checkbox", () => {
cy.get(`.stCheckbox .stTooltipIcon`)
.invoke("show")
.click();
cy.get(`.stCheckbox .stTooltipIcon`).invoke("show").click();
cy.get("[data-testid=stMarkdownContainer]").should(
"contain",
tooltipTextBlock1
);
});

it("Display text properly on tooltips on radio", () => {
cy.get(`.stRadio .stTooltipIcon`)
.invoke("show")
.click();
cy.get(`.stRadio .stTooltipIcon`).invoke("show").click();
cy.get("[data-testid=stMarkdownContainer] .stCodeBlock").should(
"contain",
tooltipCodeBlock2
);
});

it("Display text properly on tooltips on Selectbox", () => {
cy.get(`.stSelectbox .stTooltipIcon`)
.invoke("show")
.click();
cy.get(`.stSelectbox .stTooltipIcon`).invoke("show").click();
cy.get("[data-testid=stMarkdownContainer]").should(
"contain",
defaultTooltip
);
});

it("Display text properly on tooltips on timeinput", () => {
cy.get(`.stTimeInput .stTooltipIcon`)
.invoke("show")
.click();
cy.get(`.stTimeInput .stTooltipIcon`).invoke("show").click();
cy.get("[data-testid=stMarkdownContainer] .stCodeBlock").should(
"contain",
tooltipCodeBlock1
);
});

it("Display text properly on tooltips on dateinput", () => {
cy.get(`.stDateInput .stTooltipIcon`)
.invoke("show")
.click();
cy.get(`.stDateInput .stTooltipIcon`).invoke("show").click();
cy.get("[data-testid=stMarkdownContainer]").should(
"contain",
tooltipTextBlock1
Expand All @@ -179,10 +170,7 @@ describe("tooltip text with dedent on widgets", () => {

//This one needs to be the first slider
it("Display text properly on tooltips on sliders", () => {
cy.get(`.stSlider .stTooltipIcon`)
.eq(0)
.invoke("show")
.click();
cy.get(`.stSlider .stTooltipIcon`).eq(0).invoke("show").click();
cy.get("[data-testid=stMarkdownContainer] .stCodeBlock").should(
"contain",
tooltipCodeBlock2
Expand Down Expand Up @@ -213,7 +201,7 @@ describe("tooltip text with dedent on widgets", () => {
cy.get(`.stMultiSelect .stTooltipIcon`)
.invoke("show")
.click({ force: true })
.trigger("mouseover", {force: true});
.trigger("mouseover", { force: true });
cy.get("[data-testid=stMarkdownContainer] .stCodeBlock").should(
"contain",
tooltipCodeBlock1
Expand Down Expand Up @@ -243,15 +231,20 @@ describe("tooltip text with dedent on widgets", () => {
});

it("Display text properly on tooltips on button", () => {
cy.get(".stButton [data-testid=tooltipHoverTarget]").trigger("mouseover", { force: true });
cy.get(".stButton [data-testid=stTooltipHoverTarget]").trigger(
"mouseover",
{ force: true }
);
cy.get("[data-testid=stMarkdownContainer]").should(
"contain",
tooltipTextBlock2
);
});

it("Display text properly on tooltips on metric", () => {
cy.get("[data-testid=stMetricLabel] [data-testid=tooltipHoverTarget]").trigger("mouseover", { force: true });
cy.get(
"[data-testid=stMetricLabel] [data-testid=stTooltipHoverTarget]"
).trigger("mouseover", { force: true });
cy.get("[data-testid=stMarkdownContainer]").should(
"contain",
tooltipTextBlock2
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions e2e_playwright/st_popover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
#
# 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 numpy as np
import pandas as pd

import streamlit as st

# Create random dataframe:
np.random.seed(0)
df = pd.DataFrame(np.random.randn(50, 5), columns=["a", "b", "c", "d", "e"])

st.popover("popover 1 (empty)")

with st.popover("popover 2 (use_container_width)", use_container_width=True):
st.markdown("Hello")

with st.popover(
"popover 3 (with widgets)",
):
st.markdown("Hello World 👋")
text = st.text_input("Text input")
col1, col2, col3 = st.columns(3)
col1.text_input("Column 1")
col2.text_input("Column 2")
col3.text_input("Column 3")
st.selectbox("Selectbox", ["a", "b", "c"])


with st.popover("popover 4 (with dataframe)", help="help text"):
st.markdown("Popover with dataframe")
st.dataframe(df, use_container_width=True)
st.image(np.repeat(0, 100).reshape(10, 10))

with st.sidebar.popover("popover 5 (in sidebar)"):
st.markdown("Popover in sidebar with dataframe")
st.dataframe(df, use_container_width=True)

with st.popover("popover 6 (disabled)", disabled=True):
st.markdown("Hello World 👋")

with st.expander("Output"):
st.markdown(text)
144 changes: 144 additions & 0 deletions e2e_playwright/st_popover_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
#
# 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.

from playwright.sync_api import Page, expect

from e2e_playwright.conftest import ImageCompareFunction, wait_for_app_run


def test_popover_button_rendering(
themed_app: Page, assert_snapshot: ImageCompareFunction
):
"""Test that the popover buttons are correctly rendered via screenshot matching."""
popover_elements = themed_app.get_by_test_id("stPopover")
expect(popover_elements).to_have_count(6)

assert_snapshot(popover_elements.nth(0), name="st_popover-sidebar")
assert_snapshot(popover_elements.nth(1), name="st_popover-empty")
assert_snapshot(popover_elements.nth(2), name="st_popover-use_container_width")
assert_snapshot(popover_elements.nth(3), name="st_popover-normal")
raethlein marked this conversation as resolved.
Show resolved Hide resolved
# Popover button 4 is almost the same as 3, so we don't need to test it
assert_snapshot(popover_elements.nth(5), name="st_popover-disabled")


def test_popover_container_rendering(
themed_app: Page, assert_snapshot: ImageCompareFunction
):
"""Test that the popover container is correctly rendered via screenshot matching."""
# Get the widgets popover container:
popover_element = themed_app.get_by_test_id("stPopover").nth(3)
# Click the button to open it:
popover_element.locator("button").click()

# Check that it is open:
popover_container = themed_app.get_by_test_id("stPopoverBody")
expect(popover_container).to_be_visible()
expect(popover_container.get_by_test_id("stMarkdown")).to_have_text("Hello World 👋")

# Click somewhere outside the close popover container:
themed_app.get_by_test_id("stApp").click(position={"x": 0, "y": 0})
expect(popover_container).not_to_be_visible()

# Click the button to open it:
popover_element.locator("button").click()

popover_container = themed_app.get_by_test_id("stPopoverBody")
expect(popover_container).to_be_visible()
expect(popover_container.get_by_test_id("stMarkdown")).to_have_text("Hello World 👋")
expect(popover_container.get_by_test_id("stTextInput")).to_have_count(4)

assert_snapshot(popover_container, name="st_popover-container")


def test_applying_changes_from_popover_container(app: Page):
"""Test that changes made in the popover container are applied correctly."""
# Get the widgets popover container:
popover_element = app.get_by_test_id("stPopover").nth(3)
# Click the button to open it:
popover_element.locator("button").click()

# Check that it is open:
popover_container = app.get_by_test_id("stPopoverBody")
expect(popover_container).to_be_visible()
expect(popover_container.get_by_test_id("stMarkdown")).to_have_text("Hello World 👋")

# Fill in the text:
text_input_element = popover_container.get_by_test_id("stTextInput").nth(0)
text_input_element.locator("input").first.fill("Input text in popover")
wait_for_app_run(app)

# Click somewhere outside the close popover container:
app.get_by_test_id("stApp").click(position={"x": 0, "y": 0})
expect(popover_container).not_to_be_visible()

# Click the button to open it:
popover_element.locator("button").click()

popover_container = app.get_by_test_id("stPopoverBody")
expect(popover_container).to_be_visible()

# Write a text into a text input
text_input_element = popover_container.get_by_test_id("stTextInput").nth(0)
text_input_element.locator("input").first.fill("Input text in popover")
wait_for_app_run(app)

# Check that it is still open after rerun:
expect(popover_container).to_be_visible()
expect(popover_container.get_by_test_id("stMarkdown")).to_have_text("Hello World 👋")

# Click somewhere outside the close popover container
app.get_by_test_id("stApp").click(position={"x": 0, "y": 0})
expect(popover_container).not_to_be_visible()

# The main app should render this text:
expect(app.get_by_test_id("stExpander").get_by_test_id("stMarkdown")).to_have_text(
"Input text in popover"
)


def test_fullscreen_mode_is_disabled_in_popover(app: Page):
"""Test that the fullscreen mode is disabled within a popover container."""
# Get the fullscreen elements popover container:
popover_element = app.get_by_test_id("stPopover").nth(4)
# Click the button to open it:
popover_element.get_by_test_id("baseButton-secondary").first.click()

popover_container = app.get_by_test_id("stPopoverBody")
expect(popover_container).to_be_visible()

# check that the image does not have the fullscreen button
expect(popover_container.get_by_test_id("StyledFullScreenButton")).to_have_count(0)

# Check dataframe toolbar:
dataframe_element = popover_container.get_by_test_id("stDataFrame").nth(0)
expect(dataframe_element).to_be_visible()
dataframe_toolbar = dataframe_element.get_by_test_id("stElementToolbar")
# Hover over dataframe
dataframe_element.hover()
# Should only have two buttons, search + download CSV
expect(dataframe_toolbar.get_by_test_id("stElementToolbarButton")).to_have_count(2)


def test_show_tooltip_on_hover(app: Page):
"""Test that the tooltip is shown when hovering over a popover button."""
popover_button = (
app.get_by_test_id("stPopover")
.nth(4)
.get_by_test_id("baseButton-secondary")
.first
)
# Click the button to open it:
popover_button.hover()

expect(app.get_by_test_id("stTooltipContent")).to_have_text("help text")
14 changes: 9 additions & 5 deletions frontend/app/src/components/StatusWidget/StatusWidget.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe("StatusWidget element", () => {

expect(screen.getByTestId("stStatusWidget")).toBeInTheDocument()
expect(screen.getByText("Connecting")).toBeInTheDocument()
expect(screen.getByTestId("tooltipHoverTarget")).toBeInTheDocument()
expect(screen.getByTestId("stTooltipHoverTarget")).toBeInTheDocument()
})

it("renders its tooltip when disconnected", () => {
Expand All @@ -71,18 +71,20 @@ describe("StatusWidget element", () => {

expect(screen.getByTestId("stStatusWidget")).toBeInTheDocument()
expect(screen.getByText("Error")).toBeInTheDocument()
expect(screen.getByTestId("tooltipHoverTarget")).toBeInTheDocument()
expect(screen.getByTestId("stTooltipHoverTarget")).toBeInTheDocument()
})

it("renders its tooltip when running and minimized", () => {
render(<StatusWidget {...getProps()} />)
expect(screen.queryByTestId("tooltipHoverTarget")).not.toBeInTheDocument()
expect(
screen.queryByTestId("stTooltipHoverTarget")
).not.toBeInTheDocument()

// Set scrollY so shouldMinimize returns true
global.scrollY = 50

render(<StatusWidget {...getProps()} />)
expect(screen.getByTestId("tooltipHoverTarget")).toBeInTheDocument()
expect(screen.getByTestId("stTooltipHoverTarget")).toBeInTheDocument()

// Reset scrollY for following tests not impacted
global.scrollY = 0
Expand All @@ -95,7 +97,9 @@ describe("StatusWidget element", () => {
/>
)

expect(screen.queryByTestId("tooltipHoverTarget")).not.toBeInTheDocument()
expect(
screen.queryByTestId("stTooltipHoverTarget")
).not.toBeInTheDocument()
})

it("sets and unsets the sessionEventConnection", () => {
Expand Down
Loading
Loading