Skip to content

Commit acab56d

Browse files
authored
refactor: shiny create and shiny add test supporting functions (posit-dev#1629)
1 parent 4b86ebe commit acab56d

File tree

6 files changed

+268
-236
lines changed

6 files changed

+268
-236
lines changed

shiny/_main.py

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -484,33 +484,6 @@ def try_import_module(module: str) -> Optional[types.ModuleType]:
484484
return importlib.import_module(module)
485485

486486

487-
# The template choices are defined here instead of in `_template_utiles.py` in
488-
# order to delay loading the questionary package until shiny create is called.
489-
490-
# These templates are copied over fromt the `shiny/templates/app_templates`
491-
# directory. The process for adding new ones is to add your app folder to
492-
# that directory, and then add another entry to this dictionary.
493-
app_template_choices = {
494-
"Basic app": "basic-app",
495-
"Sidebar layout": "basic-sidebar",
496-
"Basic dashboard": "dashboard",
497-
"Intermediate dashboard": "dashboard-tips",
498-
"Navigating multiple pages/panels": "basic-navigation",
499-
"Custom JavaScript component ...": "js-component",
500-
"Choose from the Shiny Templates website": "external-gallery",
501-
}
502-
503-
# These are templates which produce a Python package and have content filled in at
504-
# various places based on the user input. You can add new ones by following the
505-
# examples in `shiny/templates/package-templates` and then adding entries to this
506-
# dictionary.
507-
package_template_choices = {
508-
"Input component": "js-input",
509-
"Output component": "js-output",
510-
"React component": "js-react",
511-
}
512-
513-
514487
@main.group(help="""Add files to enhance your Shiny app.""")
515488
def add() -> None:
516489
pass
@@ -545,7 +518,7 @@ def test(
545518
app: Path | None,
546519
test_file: Path | None,
547520
) -> None:
548-
from ._template_utils import add_test_file
521+
from ._main_add_test import add_test_file
549522

550523
add_test_file(app_file=app, test_file=test_file)
551524

@@ -609,7 +582,7 @@ def create(
609582
dir: Optional[Path | str] = None,
610583
package_name: Optional[str] = None,
611584
) -> None:
612-
from ._template_utils import use_template_github, use_template_internal
585+
from ._main_create import use_template_github, use_template_internal
613586

614587
print(f"dir is {dir}")
615588

shiny/_main_add_test.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import sys
5+
from pathlib import Path
6+
7+
import click
8+
import questionary
9+
10+
from ._main_utils import cli_action, cli_bold, cli_code, path_rel_wd
11+
12+
13+
def add_test_file(
14+
*,
15+
app_file: Path | None,
16+
test_file: Path | None,
17+
):
18+
if app_file is None:
19+
20+
def path_exists(x: Path) -> bool | str:
21+
if not isinstance(x, (str, Path)):
22+
return False
23+
if Path(x).is_dir():
24+
return "Please provide a file path to your Shiny app"
25+
return Path(x).exists() or f"Shiny app file can not be found: {x}"
26+
27+
app_file_val = questionary.path(
28+
"Enter the path to the app file:",
29+
default=path_rel_wd("app.py"),
30+
validate=path_exists,
31+
).ask()
32+
else:
33+
app_file_val = app_file
34+
# User quit early
35+
if app_file_val is None:
36+
sys.exit(1)
37+
app_file = Path(app_file_val)
38+
39+
if test_file is None:
40+
41+
def path_does_not_exist(x: Path) -> bool | str:
42+
if not isinstance(x, (str, Path)):
43+
return False
44+
if Path(x).is_dir():
45+
return "Please provide a file path for your test file."
46+
if Path(x).exists():
47+
return "Test file already exists. Please provide a new file name."
48+
if not Path(x).name.startswith("test_"):
49+
return "Test file must start with 'test_'"
50+
return True
51+
52+
test_file_val = questionary.path(
53+
"Enter the path to the test file:",
54+
default=path_rel_wd(
55+
os.path.relpath(app_file.parent / "tests" / "test_app.py", ".")
56+
),
57+
validate=path_does_not_exist,
58+
).ask()
59+
else:
60+
test_file_val = test_file
61+
62+
# User quit early
63+
if test_file_val is None:
64+
sys.exit(1)
65+
test_file = Path(test_file_val)
66+
67+
# Make sure app file exists
68+
if not app_file.exists():
69+
raise FileExistsError("App file does not exist: ", test_file)
70+
# Make sure output test file doesn't exist
71+
if test_file.exists():
72+
raise FileExistsError("Test file already exists: ", test_file)
73+
if not test_file.name.startswith("test_"):
74+
return "Test file must start with 'test_'"
75+
76+
# if app path directory is the same as the test file directory, use `local_app`
77+
# otherwise, use `create_app_fixture`
78+
is_same_dir = app_file.parent == test_file.parent
79+
80+
test_name = test_file.name.replace(".py", "")
81+
rel_path = os.path.relpath(app_file, test_file.parent)
82+
83+
template = (
84+
f"""\
85+
from playwright.sync_api import Page
86+
87+
from shiny.playwright import controller
88+
from shiny.run import ShinyAppProc
89+
90+
91+
def {test_name}(page: Page, local_app: ShinyAppProc):
92+
93+
page.goto(local_app.url)
94+
# Add test code here
95+
"""
96+
if is_same_dir
97+
else f"""\
98+
from playwright.sync_api import Page
99+
100+
from shiny.playwright import controller
101+
from shiny.pytest import create_app_fixture
102+
from shiny.run import ShinyAppProc
103+
104+
app = create_app_fixture("{rel_path}")
105+
106+
107+
def {test_name}(page: Page, app: ShinyAppProc):
108+
109+
page.goto(app.url)
110+
# Add test code here
111+
"""
112+
)
113+
# Make sure test file directory exists
114+
test_file.parent.mkdir(parents=True, exist_ok=True)
115+
116+
# Write template to test file
117+
test_file.write_text(template)
118+
119+
# next steps
120+
click.echo()
121+
click.echo(cli_action(cli_bold("Next steps:")))
122+
click.echo(f"- Run {cli_code('pytest')} in your terminal to run all the tests")

0 commit comments

Comments
 (0)