From 5aaa2497e6c8d90e8bef582ae808bce05cbad8d2 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 25 Aug 2025 11:50:05 -0400 Subject: [PATCH 01/14] including notes --- shiny/ui/_include_helpers.py | 25 +++++++++++++++++-- .../shiny/bugs/2061-css-js-inclusion/app.py | 22 ++++++++++++++++ .../2061-css-js-inclusion/www/customjs.js | 1 + .../bugs/2061-css-js-inclusion/www/style.css | 19 ++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py create mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/www/customjs.js create mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/www/style.css diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index 3c0c9409a..826482c9b 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -89,6 +89,10 @@ def include_js( if method == "inline": return tags.script(read_utf8(file_path), **kwargs) + # QUESTION: Do we have access to the session or user at this point? Can write based on that and then give them full perms? + # Where does cleanup happen?! Seems like maybe that needs to be included before we make new folders? Check the date? Tell if active + # session using them?! + include_files = method == "link_files" path_dest, hash = maybe_copy_files(file_path, include_files) @@ -217,21 +221,35 @@ def maybe_copy_files(path: Path | str, include_files: bool) -> tuple[str, str]: # To avoid unnecessary work when the same file is included multiple times, # use a directory scoped by a hash of the file. + # We need to make tempdir deterministic if we use the pythong TemporaryDirectory class so we can still tell if the hash has changed + # We want the temp dir unique to the process, hash should be the only thing that is dynamic based on the file contents. + # Cleanup done using __del__? + # Also make perms so that others can read it + # Double check clean-up based on process ending tmpdir = os.path.join(tempfile.gettempdir(), "shiny_include_files", hash) path_dest = os.path.join(tmpdir, os.path.basename(path)) + print("tmpdir, path_dest: ", tmpdir, path_dest) # Since the hash/tmpdir should represent all the files in the path's directory, # we can simply return here if os.path.exists(path_dest): + print("path exists: ", path_dest, hash) return path_dest, hash # Otherwise, make sure we have a clean slate + # We only hit this if tempdir/ exists but not the file (i.e. style.css) if os.path.exists(tmpdir): + print("clean the slate, tmpdir", tmpdir) shutil.rmtree(tmpdir) if include_files: + print("include files true") + # TODO: Switch this to tempfile.mkdtmp? This will scope perms to the userID + # QUESTION what is the user id? The system user? How does that work in Connect? shutil.copytree(os.path.dirname(path), tmpdir) else: + print("include files false") + # This should probably use the temp file syntax os.makedirs(tmpdir, exist_ok=True) shutil.copy(path, path_dest) @@ -240,11 +258,14 @@ def maybe_copy_files(path: Path | str, include_files: bool) -> tuple[str, str]: def get_hash(path: Path | str, include_files: bool) -> str: if include_files: - key = get_file_key(path) - else: dir = os.path.dirname(path) + # This is recursively listing everything in the dir and making that part of the hash even though we're not pulling it over + # Ex. we make a hash, we copy over one file, then we jsut don't keep going 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..438af56dd --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py @@ -0,0 +1,22 @@ +from shiny import App, ui + +# Define the UI +app_ui = ui.page_fluid( + ui.include_css("./www/style.css"), # Reference the external CSS file + ui.include_js("./www/customjs.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, output, session): + pass + + +# Create and run the app +app = App(app_ui, server) diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/customjs.js b/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/customjs.js new file mode 100644 index 000000000..dbb1b36fa --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/customjs.js @@ -0,0 +1 @@ +console.log("heyo") \ No newline at end of file diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/style.css b/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/style.css new file mode 100644 index 000000000..e86131d86 --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/style.css @@ -0,0 +1,19 @@ + +body { + background-color: #f0f8ff; + font-family: Arial, sans-serif; +} + +h1 { + color: #4682b4; + 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); +} From cc15245fc397f92ce84850ce99dac2a534032564 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 25 Aug 2025 12:23:36 -0400 Subject: [PATCH 02/14] Removing notes to self --- shiny/ui/_include_helpers.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index 826482c9b..0a9be9075 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -89,10 +89,6 @@ def include_js( if method == "inline": return tags.script(read_utf8(file_path), **kwargs) - # QUESTION: Do we have access to the session or user at this point? Can write based on that and then give them full perms? - # Where does cleanup happen?! Seems like maybe that needs to be included before we make new folders? Check the date? Tell if active - # session using them?! - include_files = method == "link_files" path_dest, hash = maybe_copy_files(file_path, include_files) @@ -219,13 +215,6 @@ def create_include_dependency( def maybe_copy_files(path: Path | str, include_files: bool) -> tuple[str, str]: hash = get_hash(path, include_files) - # To avoid unnecessary work when the same file is included multiple times, - # use a directory scoped by a hash of the file. - # We need to make tempdir deterministic if we use the pythong TemporaryDirectory class so we can still tell if the hash has changed - # We want the temp dir unique to the process, hash should be the only thing that is dynamic based on the file contents. - # Cleanup done using __del__? - # Also make perms so that others can read it - # Double check clean-up based on process ending tmpdir = os.path.join(tempfile.gettempdir(), "shiny_include_files", hash) path_dest = os.path.join(tmpdir, os.path.basename(path)) print("tmpdir, path_dest: ", tmpdir, path_dest) @@ -233,23 +222,15 @@ def maybe_copy_files(path: Path | str, include_files: bool) -> tuple[str, str]: # Since the hash/tmpdir should represent all the files in the path's directory, # we can simply return here if os.path.exists(path_dest): - print("path exists: ", path_dest, hash) return path_dest, hash # Otherwise, make sure we have a clean slate - # We only hit this if tempdir/ exists but not the file (i.e. style.css) if os.path.exists(tmpdir): - print("clean the slate, tmpdir", tmpdir) shutil.rmtree(tmpdir) if include_files: - print("include files true") - # TODO: Switch this to tempfile.mkdtmp? This will scope perms to the userID - # QUESTION what is the user id? The system user? How does that work in Connect? shutil.copytree(os.path.dirname(path), tmpdir) else: - print("include files false") - # This should probably use the temp file syntax os.makedirs(tmpdir, exist_ok=True) shutil.copy(path, path_dest) @@ -259,8 +240,6 @@ def maybe_copy_files(path: Path | str, include_files: bool) -> tuple[str, str]: def get_hash(path: Path | str, include_files: bool) -> str: if include_files: dir = os.path.dirname(path) - # This is recursively listing everything in the dir and making that part of the hash even though we're not pulling it over - # Ex. we make a hash, we copy over one file, then we jsut don't keep going files = glob.iglob(os.path.join(dir, "**"), recursive=True) key = "\n".join([get_file_key(x) for x in files]) else: From e6b8260add9a2a25bf047996ed8cba7037910266 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 25 Aug 2025 12:27:34 -0400 Subject: [PATCH 03/14] cleanup --- shiny/ui/_include_helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index 0a9be9075..33840b94b 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -215,9 +215,10 @@ def create_include_dependency( def maybe_copy_files(path: Path | str, include_files: bool) -> tuple[str, str]: hash = get_hash(path, include_files) + # To avoid unnecessary work when the same file is included multiple times, + # use a directory scoped by a hash of the file. tmpdir = os.path.join(tempfile.gettempdir(), "shiny_include_files", hash) path_dest = os.path.join(tmpdir, os.path.basename(path)) - print("tmpdir, path_dest: ", tmpdir, path_dest) # Since the hash/tmpdir should represent all the files in the path's directory, # we can simply return here From 03839f05d8e7cf5d97ed9aff6c5751b423fb33a4 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Tue, 26 Aug 2025 14:00:40 -0400 Subject: [PATCH 04/14] Fixing types --- tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py index 438af56dd..41696c8ca 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py @@ -1,4 +1,4 @@ -from shiny import App, ui +from shiny import App, Inputs, Outputs, Session, ui # Define the UI app_ui = ui.page_fluid( @@ -14,7 +14,7 @@ # Define the server -def server(input, output, session): +def server(input: Inputs, output: Outputs, session: Session): pass From c1a11a1cd7b0678b6c8ee63781ec3ff54d2694f8 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 27 Aug 2025 12:27:52 -0400 Subject: [PATCH 05/14] Updating tests --- tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py | 4 ++-- .../shiny/bugs/2061-css-js-inclusion/css/more.css | 3 +++ .../bugs/2061-css-js-inclusion/{www => css}/style.css | 4 ++-- .../shiny/bugs/2061-css-js-inclusion/js/customjs.js | 4 ++++ .../bugs/2061-css-js-inclusion/test_css-js-inclusion.py | 9 +++++++++ .../shiny/bugs/2061-css-js-inclusion/www/customjs.js | 1 - 6 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/css/more.css rename tests/playwright/shiny/bugs/2061-css-js-inclusion/{www => css}/style.css (84%) create mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs.js create mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/test_css-js-inclusion.py delete mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/www/customjs.js diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py index 41696c8ca..847bcc6eb 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py @@ -2,8 +2,8 @@ # Define the UI app_ui = ui.page_fluid( - ui.include_css("./www/style.css"), # Reference the external CSS file - ui.include_js("./www/customjs.js"), + ui.include_css("./css/style.css", method="link_files"), + ui.include_js("./js/customjs.js", method="link_files"), ui.h1("Simple Shiny App with External CSS"), ui.div( ui.p("This is a simple Shiny app that demonstrates ui.include_css()"), diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/more.css b/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/more.css new file mode 100644 index 000000000..3eda1891b --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/more.css @@ -0,0 +1,3 @@ +body { + background-color: #c8e1f7; +} \ No newline at end of file diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/style.css b/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/style.css similarity index 84% rename from tests/playwright/shiny/bugs/2061-css-js-inclusion/www/style.css rename to tests/playwright/shiny/bugs/2061-css-js-inclusion/css/style.css index e86131d86..810040c7a 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/style.css +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/style.css @@ -1,11 +1,11 @@ +@import url("more.css"); body { - background-color: #f0f8ff; font-family: Arial, sans-serif; } h1 { - color: #4682b4; + color: black; border-bottom: 2px solid #4682b4; padding-bottom: 10px; } 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..70925a499 --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs.js @@ -0,0 +1,4 @@ +const newParagraph = document.createElement('p'); +newParagraph.textContent = 'Heyo!'; +const bodyElement = document.body; +bodyElement.appendChild(newParagraph); 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..b144f78f6 --- /dev/null +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/test_css-js-inclusion.py @@ -0,0 +1,9 @@ +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")).to_have_text("Heyo!") diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/customjs.js b/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/customjs.js deleted file mode 100644 index dbb1b36fa..000000000 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/www/customjs.js +++ /dev/null @@ -1 +0,0 @@ -console.log("heyo") \ No newline at end of file From ff8581eae0efcf4f9ce522187512f2d02f7a51b6 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 27 Aug 2025 12:49:29 -0400 Subject: [PATCH 06/14] Debugging --- tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py index 847bcc6eb..aa823bfd0 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py @@ -1,9 +1,13 @@ from shiny import App, Inputs, Outputs, Session, ui +from pathlib import Path + +js_file = Path(__file__).parent / "js" / "customjs.js" +css_file = Path(__file__).parent / "css" / "style.css" # Define the UI app_ui = ui.page_fluid( - ui.include_css("./css/style.css", method="link_files"), - ui.include_js("./js/customjs.js", method="link_files"), + ui.include_css(css_file, method="link_files"), + ui.include_js(js_file, method="link_files"), ui.h1("Simple Shiny App with External CSS"), ui.div( ui.p("This is a simple Shiny app that demonstrates ui.include_css()"), From c888903703f34a9d71e936b363df6a284b4663c2 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 27 Aug 2025 12:56:52 -0400 Subject: [PATCH 07/14] fixes --- tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py index aa823bfd0..c8e979769 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py @@ -1,6 +1,7 @@ -from shiny import App, Inputs, Outputs, Session, ui from pathlib import Path +from shiny import App, Inputs, Outputs, Session, ui + js_file = Path(__file__).parent / "js" / "customjs.js" css_file = Path(__file__).parent / "css" / "style.css" From f00c2b97fcd5198bf89fbe0cb5fde3b0d2660448 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Thu, 28 Aug 2025 14:10:19 -0400 Subject: [PATCH 08/14] Minor update --- .../playwright/shiny/bugs/2061-css-js-inclusion/js/customjs.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 index 70925a499..c7e8ae251 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs.js +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs.js @@ -1,4 +1,3 @@ const newParagraph = document.createElement('p'); newParagraph.textContent = 'Heyo!'; -const bodyElement = document.body; -bodyElement.appendChild(newParagraph); +document.body.appendChild(newParagraph); From 6488c37ee7e66c40ec3f37e9c18611d802a0216f Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Thu, 28 Aug 2025 16:18:38 -0400 Subject: [PATCH 09/14] Updating the js scripts --- shiny/ui/_include_helpers.py | 7 +++---- tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py | 6 ++++++ .../shiny/bugs/2061-css-js-inclusion/css/more.css | 3 --- .../shiny/bugs/2061-css-js-inclusion/css/style.css | 2 -- .../shiny/bugs/2061-css-js-inclusion/js/customjs2.js | 3 +++ .../bugs/2061-css-js-inclusion/test_css-js-inclusion.py | 3 ++- 6 files changed, 14 insertions(+), 10 deletions(-) delete mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/css/more.css create mode 100644 tests/playwright/shiny/bugs/2061-css-js-inclusion/js/customjs2.js diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index 33840b94b..d232b0749 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -36,10 +36,10 @@ 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.link` 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 you should **be careful not to include files in the same directory as ``path`` @@ -48,7 +48,7 @@ def include_js( 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 accompanying 'safe' files should be located under ``/app/css/``). - * ``"inline"`` is the inline the CSS file contents within a + * ``"inline"`` is the inline the JS file contents within a :func:`~shiny.ui.tags.style` tag. **kwargs Attributes which are passed on to `~shiny.ui.tags.script`. @@ -234,7 +234,6 @@ 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 diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py index c8e979769..f6287cb56 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py @@ -3,12 +3,18 @@ from shiny import App, Inputs, Outputs, Session, ui js_file = Path(__file__).parent / "js" / "customjs.js" +# We don't know what the temp dir will be called until we make it and that's what we need to reference +# in the app (ex. we can't just reference where it is pre-copy) +js2_file = ( + ui.include_js(js_file, method="link_files").attrs["src"][:-11] + "customjs2.js" +) css_file = Path(__file__).parent / "css" / "style.css" # Define the UI app_ui = ui.page_fluid( ui.include_css(css_file, method="link_files"), ui.include_js(js_file, method="link_files"), + ui.tags.script(src=str(js2_file)), ui.h1("Simple Shiny App with External CSS"), ui.div( ui.p("This is a simple Shiny app that demonstrates ui.include_css()"), diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/more.css b/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/more.css deleted file mode 100644 index 3eda1891b..000000000 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/more.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - background-color: #c8e1f7; -} \ No newline at end of file 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 index 810040c7a..a7520b367 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/style.css +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/css/style.css @@ -1,5 +1,3 @@ -@import url("more.css"); - body { font-family: Arial, sans-serif; } 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 index b144f78f6..af91c1c2f 100644 --- 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 @@ -6,4 +6,5 @@ def test_inclusion(page: Page, local_app: ShinyAppProc) -> None: page.goto(local_app.url) - expect(page.locator("body > p")).to_have_text("Heyo!") + expect(page.locator("body > p").first).to_have_text("Heyo!") + expect(page.locator("body > p").last).to_have_text("Also here!") From 5418fe4f8c86718a80390e15747ed5ba0c9756cd Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Fri, 29 Aug 2025 09:40:40 -0400 Subject: [PATCH 10/14] updates --- .../shiny/bugs/2061-css-js-inclusion/app.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py index f6287cb56..f893939df 100644 --- a/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py +++ b/tests/playwright/shiny/bugs/2061-css-js-inclusion/app.py @@ -2,19 +2,24 @@ from shiny import App, Inputs, Outputs, Session, ui -js_file = Path(__file__).parent / "js" / "customjs.js" -# We don't know what the temp dir will be called until we make it and that's what we need to reference -# in the app (ex. we can't just reference where it is pre-copy) -js2_file = ( - ui.include_js(js_file, method="link_files").attrs["src"][:-11] + "customjs2.js" +custom_css = ui.include_css( + Path(__file__).parent / "css" / "style.css", + method="link_files", ) -css_file = Path(__file__).parent / "css" / "style.css" + +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( - ui.include_css(css_file, method="link_files"), - ui.include_js(js_file, method="link_files"), - ui.tags.script(src=str(js2_file)), + 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()"), From 7525f4162c92028ca148406e64f86ae4ae7d1d6b Mon Sep 17 00:00:00 2001 From: E Nelson Date: Tue, 2 Sep 2025 16:31:19 -0400 Subject: [PATCH 11/14] Update shiny/ui/_include_helpers.py Co-authored-by: Carson Sievert --- shiny/ui/_include_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index d232b0749..33c88dcb5 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -36,7 +36,7 @@ def include_js( method One of the following: - * ``"link"`` is the link to the JS 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 JS file to From afe3018c5989201bf294fc2d2bacc6ff70523a4b Mon Sep 17 00:00:00 2001 From: E Nelson Date: Tue, 2 Sep 2025 16:31:27 -0400 Subject: [PATCH 12/14] Update shiny/ui/_include_helpers.py Co-authored-by: Carson Sievert --- shiny/ui/_include_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index 33c88dcb5..2ea08683a 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -49,7 +49,7 @@ def include_js( should be somewhere like ``/app/css/custom.css`` (and all the other relevant accompanying 'safe' files should be located under ``/app/css/``). * ``"inline"`` is the inline the JS file contents within a - :func:`~shiny.ui.tags.style` tag. + :func:`~shiny.ui.tags.script` tag. **kwargs Attributes which are passed on to `~shiny.ui.tags.script`. From 8e39226edc92a5eed86a642febdf1c1976f07d62 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Tue, 2 Sep 2025 16:31:35 -0400 Subject: [PATCH 13/14] Update shiny/ui/_include_helpers.py Co-authored-by: Carson Sievert --- shiny/ui/_include_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index 2ea08683a..bcb6b152d 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -41,7 +41,7 @@ def include_js( cache the file. * ``"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 From 7ae56713834f32278f6c2feddea0cbcac4c3198a Mon Sep 17 00:00:00 2001 From: E Nelson Date: Tue, 2 Sep 2025 16:31:42 -0400 Subject: [PATCH 14/14] Update shiny/ui/_include_helpers.py Co-authored-by: Carson Sievert --- shiny/ui/_include_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/ui/_include_helpers.py b/shiny/ui/_include_helpers.py index bcb6b152d..ca09938c2 100644 --- a/shiny/ui/_include_helpers.py +++ b/shiny/ui/_include_helpers.py @@ -46,7 +46,7 @@ def include_js( 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 JS file contents within a :func:`~shiny.ui.tags.script` tag.