Skip to content

Commit 01c92b9

Browse files
Merge branch 'main' of https://github.com/facultyai/dash-bootstrap-components into carousel
2 parents a6a923d + 3187052 commit 01c92b9

File tree

336 files changed

+6928
-292
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

336 files changed

+6928
-292
lines changed

.github/workflows/doctest.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Test doc snippets
2+
3+
on:
4+
push:
5+
paths:
6+
- docs/components_page/**
7+
pull_request:
8+
paths:
9+
- docs/components_page/**
10+
11+
jobs:
12+
build-and-test:
13+
name: Build package and test snippets
14+
runs-on: 'ubuntu-latest'
15+
services:
16+
hub:
17+
image: selenium/hub:3.141.59-gold
18+
firefox:
19+
image: selenium/node-chrome:3.141.59-gold
20+
env:
21+
HUB_HOST: hub
22+
HUB_PORT: 4444
23+
steps:
24+
- uses: actions/checkout@v1
25+
- name: Use Node.js 12
26+
uses: actions/setup-node@v1
27+
with:
28+
node-version: 12.x
29+
- name: Install dependencies
30+
run: npm ci
31+
- name: Set up Python 3.8
32+
uses: actions/setup-python@v1
33+
with:
34+
python-version: 3.8
35+
- name: Install Dash
36+
run: python -m pip install -r requirements-dev.txt
37+
- name: Build dash-bootstrap-components
38+
run: npm run build
39+
- name: Install R dependencies from cran2deb4ubuntu
40+
run: |
41+
sudo add-apt-repository ppa:c2d4u.team/c2d4u4.0+
42+
sudo apt-get update
43+
sudo apt-get install r-recommended r-cran-dash
44+
- name: Install R package
45+
run: sudo R CMD INSTALL .
46+
- name: Setup Julia
47+
uses: julia-actions/setup-julia@v1
48+
with:
49+
version: '1.6.1'
50+
- name: Add Julia artifacts to repo for local install
51+
run: |
52+
git add -f deps src/*.jl Project.toml
53+
git -c user.name="GitHub Actions" -c user.email="actions@github.com" commit -m "Add Julia build artifacts"
54+
- name: Install Julia dependencies
55+
run: julia -e 'using Pkg; Pkg.add(["Dash", "DashCoreComponents", "DashHtmlComponents", "HTTP"]); Pkg.add(path=".");'
56+
- name: Test Julia installation
57+
run: julia -e 'using Dash, DashBootstrapComponents, HTTP;'
58+
- name: Install nox
59+
run: python3.8 -m pip install -U nox
60+
- name: Test R and Julia docs snippets
61+
run: nox -s doctest

docs/components_page/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
COMPONENTS = HERE / "components"
1717
TEMPLATES = HERE.parent / "templates"
1818

19-
LOREM = (COMPONENTS / "modal" / "lorem.txt").read_text()
19+
LOREM = (COMPONENTS / "modal" / "lorem.txt").read_text().strip()
2020

2121
HIGHLIGHT_JS_CSS = (
2222
"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/"

docs/components_page/api_doc.py

Lines changed: 0 additions & 45 deletions
This file was deleted.

docs/components_page/components/__tests__/__init__.py

Whitespace-only changes.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from pathlib import Path
2+
3+
4+
def py_source_to_app(py_source, env):
5+
"""
6+
Create a Dash app from a string defining the app.
7+
"""
8+
env = env or {}
9+
exec(py_source, env)
10+
return env["app"]
11+
12+
13+
def drop_keys(d):
14+
"""
15+
Layout payload for R and Julia apps has a slightly different structure. We
16+
drop some keys for parity.
17+
"""
18+
if isinstance(d, dict):
19+
return {
20+
k: drop_keys(v)
21+
for k, v in d.items()
22+
if k not in ["propNames", "package"]
23+
and v is not None
24+
and not (k == "children" and v == "")
25+
}
26+
elif isinstance(d, list):
27+
return [drop_keys(x) for x in d]
28+
return d
29+
30+
31+
def clean_path(path):
32+
if path.startswith("components/"):
33+
path = path[11:]
34+
return Path(path)
35+
36+
37+
def rename_variable(snippet_path, suffix, variable, assign_op="="):
38+
with snippet_path.open() as f:
39+
lines = f.read().split("\n")
40+
41+
new_lines = []
42+
for line in lines:
43+
if line.startswith(f"{variable} {assign_op}"):
44+
line = line.replace(
45+
f"{variable} {assign_op}", f"{variable}_{suffix} {assign_op}"
46+
)
47+
new_lines.append(line)
48+
49+
return "\n".join(new_lines)
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""
2+
Automated testing of non-Python code snippets in the docs
3+
"""
4+
import re
5+
import unittest
6+
from pathlib import Path
7+
8+
import pytest
9+
import requests
10+
from dash.testing.application_runners import JuliaRunner, RRunner
11+
12+
from .helpers import clean_path, drop_keys, py_source_to_app, rename_variable
13+
from .wrappers import JL_WRAPPER, PY_WRAPPER, R_WRAPPER
14+
15+
HERE = Path(__file__).parent
16+
17+
PATTERN = re.compile(r"{{example:(.*)}}")
18+
PARAMS = [
19+
(
20+
path,
21+
[match.split(":") for match in PATTERN.findall(path.read_text())],
22+
)
23+
for path in HERE.parent.glob("*.md")
24+
]
25+
26+
SKIP = ["components/table/kwargs.py", "components/tabs/active_tab.py"]
27+
ENVS = {
28+
"modal.md": {
29+
"LOREM": (HERE.parent / "modal" / "lorem.txt").read_text().strip()
30+
}
31+
}
32+
33+
R_PORT = 8051
34+
JL_PORT = 8053
35+
36+
37+
@pytest.fixture
38+
def dashr_server():
39+
with RRunner() as starter:
40+
starter.port = R_PORT
41+
yield starter
42+
43+
44+
@pytest.fixture
45+
def dashjl_server():
46+
with JuliaRunner() as starter:
47+
starter.port = JL_PORT
48+
yield starter
49+
50+
51+
@pytest.mark.parametrize("config", PARAMS)
52+
def test_r_snippets(dash_thread_server, dashr_server, config):
53+
md_path, data = config
54+
env = ENVS.get(md_path.name)
55+
56+
python_r_compare = []
57+
58+
# Concatenate all the snippets in the markdown file together
59+
for i, (snippet_path, name) in enumerate(data):
60+
if snippet_path in SKIP:
61+
continue
62+
63+
snippet_path = HERE.parent / clean_path(snippet_path)
64+
py_snippet = rename_variable(snippet_path, i, name)
65+
66+
r_snippet_path = snippet_path.parent / f"{snippet_path.stem}.R"
67+
68+
if r_snippet_path.exists():
69+
r_snippet = rename_variable(
70+
r_snippet_path, i, name, assign_op="<-"
71+
)
72+
python_r_compare.append((py_snippet, r_snippet, f"{name}_{i}"))
73+
74+
assert_layouts_equal(
75+
python_r_compare,
76+
dashr_server,
77+
R_WRAPPER,
78+
R_PORT,
79+
dash_thread_server,
80+
env,
81+
8050,
82+
)
83+
84+
85+
@pytest.mark.parametrize("config", PARAMS)
86+
def test_jl_snippets(dash_thread_server, dashjl_server, config):
87+
md_path, data = config
88+
env = ENVS.get(md_path.name)
89+
90+
python_jl_compare = []
91+
92+
# Concatenate all the snippets in the markdown file together
93+
for i, (snippet_path, name) in enumerate(data):
94+
if snippet_path in SKIP:
95+
continue
96+
97+
snippet_path = HERE.parent / clean_path(snippet_path)
98+
py_snippet = rename_variable(snippet_path, i, name)
99+
100+
jl_snippet_path = snippet_path.parent / f"{snippet_path.stem}.jl"
101+
102+
if jl_snippet_path.exists():
103+
jl_snippet = rename_variable(jl_snippet_path, i, name)
104+
python_jl_compare.append((py_snippet, jl_snippet, f"{name}_{i}"))
105+
106+
assert_layouts_equal(
107+
python_jl_compare,
108+
dashjl_server,
109+
JL_WRAPPER,
110+
JL_PORT,
111+
dash_thread_server,
112+
env,
113+
8052,
114+
)
115+
116+
117+
def assert_layouts_equal(
118+
compare, runner, wrapper, port, py_runner, py_env, py_port
119+
):
120+
# Get python snippet layout
121+
app = py_source_to_app(
122+
PY_WRAPPER.format(
123+
snippet="\n".join(x[0] for x in compare),
124+
components=", ".join(x[2] for x in compare),
125+
),
126+
env=py_env,
127+
)
128+
py_runner.start(app, port=py_port)
129+
py_layout = requests.get(f"{py_runner.url}/_dash-layout").json()
130+
131+
# Get other language snippet layout
132+
runner.start(
133+
wrapper.format(
134+
snippet="\n".join(x[1] for x in compare),
135+
components=", ".join(x[2] for x in compare),
136+
port=port,
137+
)
138+
)
139+
layout = requests.get(f"{runner.url}/_dash-layout").json()
140+
141+
# Test layouts match
142+
unittest.TestCase().assertDictEqual(
143+
drop_keys(py_layout), drop_keys(layout)
144+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
PY_WRAPPER = """
2+
import dash
3+
import dash_bootstrap_components as dbc
4+
5+
app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
6+
7+
{snippet}
8+
9+
app.layout = html.Div([{components}])
10+
"""
11+
12+
R_WRAPPER = """
13+
library(dash)
14+
library(dashBootstrapComponents)
15+
library(dashHtmlComponents)
16+
17+
app <- Dash$new(external_stylesheets = dbcThemes$BOOTSTRAP)
18+
19+
{snippet}
20+
21+
app$layout(htmlDiv(list({components})))
22+
app$run_server(port = {port})
23+
"""
24+
25+
JL_WRAPPER = """
26+
using Dash, DashBootstrapComponents
27+
28+
app = dash(external_stylesheets=[dbc_themes.BOOTSTRAP]);
29+
30+
{snippet}
31+
32+
app.layout = html_div([{components}]);
33+
run_server(app, "127.0.0.1", {port});
34+
"""

docs/components_page/components/alert.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,30 @@ lead: Provide contextual feedback messages for user actions with the `Alert` com
77

88
Set the color of `Alert` using the `color` argument and one of the eight supported contextual color names.
99

10-
{{example:components/alert/simple.py:alerts}}
10+
{{example:components/alert/simple.py:simple_alerts}}
1111

1212
## Link color
1313

1414
The Bootstrap `alert-link` class can be used to color match links inside alerts to the color of the alert.
1515

16-
{{example:components/alert/link.py:alerts}}
16+
{{example:components/alert/link.py:linked_alerts}}
1717

1818
## Additional content
1919

2020
Alerts can contain additional HTML elements like headings, paragraphs and dividers.
2121

22-
{{example:components/alert/content.py:alert}}
22+
{{example:components/alert/content.py:complex_alert}}
2323

2424
## Dismissing
2525

2626
Set `dismissable=True` to add a dismiss button to the alert which closes the alert on click. You can also use the `is_open` property in callbacks to show or hide the alert. By default the alert appears and disappears with a fade animation. To disable this simply set `fade=False`.
2727

28-
{{example:components/alert/dismiss.py:alert}}
28+
{{example:components/alert/dismiss.py:dimissable_alert}}
2929

3030
## Automatic dismissal
3131

3232
You can have your `Alert` components dismiss themselves by using the `duration` keyword argument. Specify a duration in milliseconds after which you would like the `Alert` to dismiss itself when it first becomes visible.
3333

34-
{{example:components/alert/auto_dismiss.py:alert}}
34+
{{example:components/alert/auto_dismiss.py:auto_dismiss_alert}}
3535

3636
{{apidoc:src/components/Alert.js}}

0 commit comments

Comments
 (0)