diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index 3c0c9409a..ca09938c2 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -36,20 +36,20 @@ def include_js( method One of the following: - * ``"link"`` is the link to the CSS file via a :func:`~shiny.ui.tags.link` tag. This + * ``"link"`` is the link to the JS file via a :func:`~shiny.ui.tags.script` tag. This method is generally preferable to ``"inline"`` since it allows the browser to cache the file. - * ``"link_files"`` is the same as ``"link"``, but also allow for the CSS file to + * ``"link_files"`` is the same as ``"link"``, but also allow for the JS file to request other files within ``path``'s immediate parent directory (e.g., - ``@import()`` another file). Note that this isn't the default behavior because + ``import`` another file). Note that this isn't the default behavior because you should **be careful not to include files in the same directory as ``path`` that contain sensitive information**. A good general rule of thumb to follow is to have ``path`` be located in a subdirectory of the app directory. For example, if the app's source is located at ``/app/app.py``, then ``path`` - should be somewhere like ``/app/css/custom.css`` (and all the other relevant + should be somewhere like ``/app/js/custom.js`` (and all the other relevant accompanying 'safe' files should be located under ``/app/css/``). - * ``"inline"`` is the inline the CSS file contents within a - :func:`~shiny.ui.tags.style` tag. + * ``"inline"`` is the inline the JS file contents within a + :func:`~shiny.ui.tags.script` tag. **kwargs Attributes which are passed on to `~shiny.ui.tags.script`. @@ -234,17 +234,17 @@ def maybe_copy_files(path: Path | str, include_files: bool) -> tuple[str, str]: else: os.makedirs(tmpdir, exist_ok=True) shutil.copy(path, path_dest) - return path_dest, hash def get_hash(path: Path | str, include_files: bool) -> str: if include_files: - key = get_file_key(path) - else: dir = os.path.dirname(path) files = glob.iglob(os.path.join(dir, "**"), recursive=True) key = "\n".join([get_file_key(x) for x in files]) + else: + key = get_file_key(path) + return hash_deterministic(key) diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py new file mode 100644 index 000000000..f893939df --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py @@ -0,0 +1,38 @@ +from pathlib import Path + +from shiny import App, Inputs, Outputs, Session, ui + +custom_css = ui.include_css( + Path(__file__).parent / "css" / "style.css", + method="link_files", +) + +custom_js = ui.include_js( + Path(__file__).parent / "js" / "customjs.js", + method="link_files", +) + +# path where the JS file's parent directory is mounted +href = custom_js.get_dependencies()[0].source_path_map()["href"] + +# Define the UI +app_ui = ui.page_fluid( + custom_css, + custom_js, + ui.tags.script(src=href + "/customjs2.js"), + ui.h1("Simple Shiny App with External CSS"), + ui.div( + ui.p("This is a simple Shiny app that demonstrates ui.include_css()"), + ui.p("The styling comes from an external CSS file!"), + class_="content", + ), +) + + +# Define the server +def server(input: Inputs, output: Outputs, session: Session): + pass + + +# Create and run the app +app = App(app_ui, server) diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/style.css b/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/style.css new file mode 100644 index 000000000..a7520b367 --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/style.css @@ -0,0 +1,17 @@ +body { + font-family: Arial, sans-serif; +} + +h1 { + color: black; + border-bottom: 2px solid #4682b4; + padding-bottom: 10px; +} + +.content { + margin: 20px; + padding: 15px; + background-color: white; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs.js b/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs.js new file mode 100644 index 000000000..c7e8ae251 --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs.js @@ -0,0 +1,3 @@ +const newParagraph = document.createElement('p'); +newParagraph.textContent = 'Heyo!'; +document.body.appendChild(newParagraph); diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs2.js b/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs2.js new file mode 100644 index 000000000..e21a8fe12 --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs2.js @@ -0,0 +1,3 @@ +const special = document.createElement('p'); +special.textContent = 'Also here!'; +document.body.appendChild(special); diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/test_css-js-inclusion.py b/tests/playwright/shiny/bugs/2061-css-js-inclusion/test_css-js-inclusion.py new file mode 100644 index 000000000..af91c1c2f --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/test_css-js-inclusion.py @@ -0,0 +1,10 @@ +from playwright.sync_api import Page, expect + +from shiny.run import ShinyAppProc + + +def test_inclusion(page: Page, local_app: ShinyAppProc) -> None: + page.goto(local_app.url) + + expect(page.locator("body > p").first).to_have_text("Heyo!") + expect(page.locator("body > p").last).to_have_text("Also here!")