-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
from __future__ import annotations | ||
|
||
from playwright.sync_api import Locator, Page | ||
from playwright.sync_api import expect as playwright_expect | ||
|
||
from .._types import PatternOrStr, Timeout | ||
from ._base import UiBase | ||
|
||
|
||
class MarkdownStream(UiBase): | ||
"""Controller for :func:`shiny.ui.MarkdownStream`.""" | ||
|
||
loc: Locator | ||
""" | ||
Playwright `Locator` for the markdown stream. | ||
""" | ||
|
||
def __init__(self, page: Page, id: str) -> None: | ||
""" | ||
Initializes a new instance of the `MarkdownStream` class. | ||
Parameters | ||
---------- | ||
page | ||
Playwright `Page` of the Shiny app. | ||
id | ||
The ID of the chat. | ||
""" | ||
super().__init__( | ||
page, | ||
id=id, | ||
loc=f"#{id}", | ||
) | ||
|
||
def expect_content( | ||
self, | ||
value: PatternOrStr, | ||
*, | ||
timeout: Timeout = None, | ||
) -> None: | ||
""" | ||
Expect the content of the markdown stream to match a value. | ||
Parameters | ||
---------- | ||
value | ||
The expected value. | ||
timeout | ||
Maximum time in milliseconds to wait for the content to match the value. | ||
""" | ||
playwright_expect(self.loc).to_have_text(value, timeout=timeout) |
63 changes: 63 additions & 0 deletions
63
tests/playwright/shiny/components/MarkdownStream/basic/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Shiny for Python | ||
|
||
[](https://pypi.org/project/shiny/) | ||
[](https://img.shields.io/github/actions/workflow/status/posit-dev/py-shiny/pytest.yaml?branch=main) | ||
[](https://anaconda.org/conda-forge/shiny) | ||
[](https://pypi.org/project/shiny/) | ||
[](https://github.com/posit-dev/py-shiny/blob/main/LICENSE) | ||
|
||
Shiny for Python is the best way to build fast, beautiful web applications in Python. You can build quickly with Shiny and create simple interactive visualizations and prototype applications in an afternoon. But unlike other frameworks targeted at data scientists, Shiny does not limit your app's growth. Shiny remains extensible enough to power large, mission-critical applications. | ||
|
||
To learn more about Shiny see the [Shiny for Python website](https://shiny.posit.co/py/). If you're new to the framework we recommend these resources: | ||
|
||
- How [Shiny is different](https://posit.co/blog/why-shiny-for-python/) from Dash and Streamlit. | ||
|
||
- How [reactive programming](https://shiny.posit.co/py/docs/reactive-programming.html) can help you build better applications. | ||
|
||
- How to [use modules](https://shiny.posit.co/py/docs/workflow-modules.html) to efficiently develop large applications. | ||
|
||
- Hosting applications for free on [shinyapps.io](https://shiny.posit.co/py/docs/deploy.html#deploy-to-shinyapps.io-cloud-hosting), [Hugging Face](https://shiny.posit.co/blog/posts/shiny-on-hugging-face/), or [Shinylive](https://shiny.posit.co/py/docs/shinylive.html). | ||
|
||
## Join the conversation | ||
|
||
If you have questions about Shiny for Python, or want to help us decide what to work on next, [join us on Discord](https://discord.gg/yMGCamUMnS). | ||
|
||
## Getting started | ||
|
||
To get started with shiny follow the [installation instructions](https://shiny.posit.co/py/docs/install-create-run.html) or just install it from pip. | ||
|
||
```sh | ||
pip install shiny | ||
``` | ||
|
||
To install the latest development version: | ||
|
||
```sh | ||
# First install htmltools, then shiny | ||
pip install git+https://github.com/posit-dev/py-htmltools.git#egg=htmltools | ||
pip install git+https://github.com/posit-dev/py-shiny.git#egg=shiny | ||
``` | ||
|
||
You can create and run your first application with `shiny create`, the CLI will ask you which template you would like to use. You can either run the app with the Shiny extension, or call `shiny run app.py --reload --launch-browser`. | ||
|
||
## Development | ||
|
||
* Shinylive built from the `main` branch: https://posit-dev.github.io/py-shiny/shinylive/py/examples/ | ||
* API documentation for the `main` branch: | ||
* https://posit-dev.github.io/py-shiny/docs/api/express/ | ||
* https://posit-dev.github.io/py-shiny/docs/api/core/ | ||
|
||
If you want to do development on Shiny for Python: | ||
|
||
```sh | ||
pip install -e ".[dev,test]" | ||
``` | ||
|
||
Additionally, you can install pre-commit hooks which will automatically reformat and lint the code when you make a commit: | ||
|
||
```sh | ||
pre-commit install | ||
|
||
# To disable: | ||
# pre-commit uninstall | ||
``` |
25 changes: 25 additions & 0 deletions
25
tests/playwright/shiny/components/MarkdownStream/basic/app.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from pathlib import Path | ||
|
||
from shiny.express import ui | ||
|
||
readme = Path(__file__).parent / "README.md" | ||
with open(readme, "r") as f: | ||
readme_chunks = f.read().replace("\n", " \n ").split(" ") | ||
|
||
|
||
# Generate words from the README.md file (with a small delay) | ||
def chunk_generator(): | ||
for chunk in readme_chunks: | ||
yield chunk + " " | ||
|
||
|
||
md = ui.MarkdownStream("shiny-readme") | ||
|
||
with ui.card( | ||
height="400px", | ||
class_="mt-3", | ||
): | ||
ui.card_header("Shiny README.md") | ||
md.ui() | ||
|
||
md.stream(chunk_generator()) |
38 changes: 38 additions & 0 deletions
38
tests/playwright/shiny/components/MarkdownStream/basic/test_stream_basic.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from playwright.sync_api import Page, expect | ||
from utils.deploy_utils import skip_on_webkit | ||
|
||
from shiny.playwright import controller | ||
from shiny.run import ShinyAppProc | ||
|
||
|
||
async def is_element_scrolled_to_bottom(page: Page, selector: str) -> bool: | ||
return await page.evaluate( | ||
"""(selector) => { | ||
const element = document.querySelector(selector); | ||
if (!element) return false; | ||
// Get the exact scroll values (rounded to handle float values) | ||
const scrollTop = Math.round(element.scrollTop); | ||
const scrollHeight = Math.round(element.scrollHeight); | ||
const clientHeight = Math.round(element.clientHeight); | ||
// Check if we're at the bottom (allowing for 1px difference due to rounding) | ||
return Math.abs((scrollTop + clientHeight) - scrollHeight) <= 1; | ||
}""", | ||
selector, | ||
) | ||
|
||
|
||
@skip_on_webkit | ||
async def test_validate_stream_basic(page: Page, local_app: ShinyAppProc) -> None: | ||
page.goto(local_app.url) | ||
|
||
stream = controller.MarkdownStream(page, "shiny-readme") | ||
|
||
expect(stream.loc).to_be_visible(timeout=30 * 1000) | ||
stream.expect_content("pip install shiny") | ||
|
||
# Check that the card body container (the parent of the markdown stream) is scrolled | ||
# all the way to the bottom | ||
is_scrolled = await is_element_scrolled_to_bottom(page, ".card-body") | ||
assert is_scrolled, "The card body container should be scrolled to the bottom" |