Skip to content

Commit 9f59ab3

Browse files
authored
Merge pull request #97 from bokeh/support_anywidget
Embed full state on initial render
2 parents b155aaf + 4df0983 commit 9f59ab3

File tree

5 files changed

+95
-11
lines changed

5 files changed

+95
-11
lines changed

.github/workflows/ipywidgets-bokeh-ci.yml

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,17 @@ jobs:
1717
os: [ubuntu-latest, macos-latest, windows-latest]
1818
node-version: [18.x]
1919

20+
defaults:
21+
run:
22+
shell: bash -el {0}
23+
2024
steps:
25+
- name: Set up conda and install base
26+
uses: conda-incubator/setup-miniconda@v2
27+
with:
28+
auto-update-conda: true
29+
activate-environment: test
30+
2131
- name: Checkout the repository
2232
uses: actions/checkout@v3
2333

@@ -27,39 +37,46 @@ jobs:
2737
node-version: ${{ matrix.node-version }}
2838

2939
- name: Upgrade npm
30-
shell: bash
3140
run: |
3241
npm install --location=global npm
3342
34-
- name: Install dependencies
43+
- name: Install JavaScript dependencies
3544
working-directory: ./ipywidgets_bokeh
36-
shell: bash
3745
run: |
3846
npm ci --no-progress
3947
40-
- name: Build ipywidgets-bokeh
48+
- name: Build ipywidgets-bokeh JavaScript
4149
working-directory: ./ipywidgets_bokeh
42-
shell: bash
4350
run: |
4451
npm run build
4552
46-
- name: Run tests
53+
- name: Run JavaScript tests
4754
if: success() || failure()
4855
working-directory: ./ipywidgets_bokeh
49-
shell: bash
5056
run: |
5157
npm run test
5258
53-
- name: Lint codebase
59+
- name: Lint the JavaScript codebase
5460
if: success() || failure()
5561
working-directory: ./ipywidgets_bokeh
56-
shell: bash
5762
run: |
5863
npm run lint
5964
65+
- name: Install dev Python dependencies & Playwright browsers
66+
if: success() || failure()
67+
run: |
68+
conda install --channel conda-forge -y pip python
69+
conda info
70+
python -m pip install --editable .[dev]
71+
python -m playwright install --with-deps
72+
73+
- name: Run Playwright & Python tests
74+
if: matrix.os != 'windows-latest'
75+
run: |
76+
pytest
77+
6078
- name: Check repository status
6179
if: success() || failure()
62-
shell: bash
6380
run: |
6481
OUTPUT=$(git status --short .)
6582
if [[ ! -z "$OUTPUT" ]]; then echo $OUTPUT; exit 1; fi

ipywidgets_bokeh/src/manager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export class WidgetManager extends HTMLManager {
155155

156156
try {
157157
const models = await this.set_state(state)
158+
await this.set_state({...state, state: state.full_state as any})
158159
for (const model of models) {
159160
if (this._model_objs.has(model.model_id))
160161
continue

ipywidgets_bokeh/widget.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class IPyWidget(LayoutDOM):
3535
def __init__(self, *, widget: Widget, **kwargs):
3636
super().__init__(**kwargs)
3737
spec = widget.get_view_spec()
38-
state = Widget.get_manager_state(widgets=[])
38+
state = Widget.get_manager_state(widgets=[widget])
39+
state["full_state"] = state["state"]
3940
state["state"] = embed.dependency_state([widget], drop_defaults=True)
4041
self.bundle = dict(spec=spec, state=state)

setup.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,19 @@ def run(self):
2929
"ipywidgets ==8.*",
3030
"ipykernel ==6.*,!=6.18.0", # until ipywidgets 8.0.6
3131
]
32+
dev_dependencies = [
33+
"anywidget>=0.3.0",
34+
"panel>=1.0.4",
35+
"pytest>=7.3.1",
36+
"pytest-cov>=4.1.0",
37+
"pytest-playwright>=0.3.3",
38+
]
3239

3340
setup_args = dict(
3441
name="ipywidgets_bokeh",
3542
version="1.4.0",
3643
install_requires=install_requires,
44+
extras_require={"dev": dev_dependencies},
3745
python_requires=">=3.9",
3846
description="Allows embedding of Jupyter widgets in Bokeh layouts.",
3947
long_description=open("README.md").read(),

tests/test_anywidget.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import time
2+
3+
import anywidget
4+
import panel as pn
5+
import traitlets
6+
from panel.io.server import serve
7+
from playwright.sync_api import expect, Page
8+
9+
10+
class CounterWidget(anywidget.AnyWidget):
11+
"""Counter widget example."""
12+
13+
_esm = """
14+
export function render(view) {
15+
let getCount = () => view.model.get("count");
16+
let button = document.createElement("button");
17+
button.classList.add("counter-button");
18+
button.innerHTML = `${getCount()}`;
19+
button.addEventListener("click", () => {
20+
view.model.set("count", getCount() + 1);
21+
view.model.save_changes();
22+
});
23+
view.model.on("change:count", () => {
24+
button.innerHTML = `${getCount()}`;
25+
});
26+
view.el.appendChild(button);
27+
}
28+
"""
29+
_css = """
30+
.counter-button {background-color: #ea580c;}
31+
.counter-button:hover {background-color: #9a3412;}
32+
"""
33+
count = traitlets.Int(default_value=0).tag(sync=True)
34+
35+
36+
def test_anywidget(page: Page) -> None:
37+
"""Test anywidget button counter example."""
38+
# Port to run the panel server on.
39+
port = 5006
40+
41+
# Create an anywidget button and make it a panel object.
42+
widget = CounterWidget()
43+
panels = pn.panel(widget)
44+
45+
# Serve the button using panel, the time delay is necessary for panel to start and
46+
# serve the widget.
47+
serve(panels=panels, port=port, show=False)
48+
time.sleep(0.2)
49+
50+
# Go to the page and locate the widget using playwright.
51+
page.goto(f"http://localhost:{port}")
52+
button = page.locator(selector=".counter-button")
53+
54+
# Click the button and monitor the results.
55+
expect(button).to_have_count(0)
56+
button.click()
57+
expect(button).to_have_count(1)

0 commit comments

Comments
 (0)