Skip to content

Commit 17cd362

Browse files
authored
Merge pull request #31 from plotly/fix-tests-wip
* Clean up tests * Copy updated `nanodash/` into each exercise folder
2 parents 956bf35 + b0de94b commit 17cd362

Some content is hidden

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

45 files changed

+1861
-1260
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*.pyc
44
*~
55
.#*
6+
*.DS_Store
67
.jekyll-cache
78
.pytest_cache
89
.ruff_cache

README.md

+31-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
# NanoDash Tutorial Exercises
1+
# Welcome to the NanoDash tutorial!
2+
3+
This tutorial will demonstrate how interactive web dashboard frameworks like Plotly Dash work, by building a simplified version of Dash itself from scratch using Python, the Flask framework, and a little bit of vanilla JavaScript.
4+
5+
## Setup
6+
7+
If you haven't already, please complete the setup instructions outlined in [SETUP.md]((https://github.com/plotly/tutorial-nanodash/blob/main/SETUP.md)) before continuing.
8+
9+
## Exercises
210

311
The 7 exercises to be completed for this tutorial are located in the directories `exercise1/` through `exercise7/`.
412

@@ -7,19 +15,20 @@ Each exercise folder contains the following:
715
- A partial copy of the NanoDash codebase (under `exerciseN/nanodash/`), containing one or more spots for you to "fill-in-the-blanks" by implementing part of the NanoDash logic
816

917
- A sample app (`exerciseN/app.py`) which will run correctly once the exercise has been completed
18+
- To run the app from the repository root: `python exerciseN/app.py`
1019

1120
- A tests file (`test_exerciseN.py`) which will pass once the exercise has been completed.
1221
- To run the tests for exercise N from the repository root: `python -m pytest exerciseN/`
13-
- To run the tests for exercise N from within the exercise directory: `python -m pytest`
1422

15-
## Note on Running Pytest
16-
We recommend running `python -m pytest` rather than just `pytest`, because it ensures using the python in your virtual environment rather than the system python.
23+
## A note on running Pytest
24+
We recommend using the command `python -m pytest` rather than just `pytest`, because it ensures using the Python in your virtual environment rather than the system Python.
1725

1826
## Exercise outline
1927

2028
Each exercise focuses on implementing a specific part of the NanoDash framework.
2129

2230
### Exercise 1: Making a basic Flask server which serves a static HTML page
31+
---
2332

2433
**Goal**: Set up a simple Flask server that serves one static HTML page.
2534

@@ -32,42 +41,45 @@ Each exercise focuses on implementing a specific part of the NanoDash framework.
3241
**Command to run tests**
3342
`python -m pytest exercise1/`
3443

35-
### Exercise 2: Implement UI components
44+
### Exercise 2: Implementing input components
45+
---
3646

37-
**Goal**: Implement basic UI component objects to use as building blocks for page layouts.
47+
**Goal**: Implement basic input components as Python objects, to be used as building blocks for interactive dashboards.
3848

3949
**Tasks**:
4050
- Review the implementations of the `Page`, `Header` and `Text` classes in `exercise2/nanodash/components.py`
41-
- Implement the `__init__()` and `html()` methods of the `TextInput` class
42-
- Implement the `__init__()` and `html()` methods of the `Dropdown` class
51+
- Implement the `html()` method of the `TextInput` class
52+
- Implement the `html()` method of the `Dropdown` class
4353

4454
**Files to modify**:
4555
- `exercise2/nanodash/components.py`
4656

4757
**Command to run tests**
4858
`python -m pytest exercise2/`
4959

50-
### Exercise 3: Implement Graph component
60+
### Exercise 3: Implementing the Graph component
61+
---
5162

5263
**Goal**: Implement the Graph component, which uses Plotly.js to display Plotly figures in the browser.
5364

5465
**Tasks**:
55-
- Implement the the `__init__()` and `html()` methods of the `Graph` class in `exercise3/nanodash/components.py`
66+
- Implement the `html()` method of the `Graph` class in `exercise3/nanodash/components.py`
5667

5768
**Files to modify**:
5869
- `exercise3/nanodash/components.py`
5970

6071
**Command to run tests**
6172
`python -m pytest exercise3/`
6273

63-
### Exercise 4: Browser to Server Communication — Gathering the page state
74+
### Exercise 4: Gathering the page state when an input changes
75+
---
6476

6577
**Goal**: Implement the Javascript logic to capture the state of all components on the page, and bundle it into a JSON request to send to the Flask server.
6678

6779
Don't worry — we've provided some useful helper functions inside the Javascript file; all you need to do is put them together in the right way.
6880

6981
**Tasks**:
70-
- Implement the `getState()` function in `exercise4/nanodash/static/index.js`.
82+
- Implement the `getState()` Javascript function in `exercise4/nanodash/static/index.js`.
7183

7284
**Files to modify**:
7385
- `exercise4/static/index.js`
@@ -76,12 +88,13 @@ Don't worry — we've provided some useful helper functions inside the Javascrip
7688
`python -m pytest exercise4/`
7789

7890
### Exercise 5: Running callbacks
91+
---
7992

8093
**Goal**: Implement the Python logic which receives the page state from the frontend, runs the necessary callbacks, and sends the results back to the frontend. Also implement the logic which allows a user to add a callback to their app.
8194

8295
**Tasks**:
83-
- Implement the `handle_change()` function in `exercise5/nanodash/nanodash.py`
84-
- Implement the `add_callback()` function in `exercise5/nanodash/nanodash.py`
96+
- Implement the `handle_change()` Python function in `exercise5/nanodash/nanodash.py`
97+
- Implement the `add_callback()` Python function in `exercise5/nanodash/nanodash.py`
8598

8699
**Files to modify**:
87100
- `exercise5/nanodash/nanodash.py`
@@ -90,19 +103,21 @@ Don't worry — we've provided some useful helper functions inside the Javascrip
90103
`python -m pytest exercise5/`
91104

92105
### Exercise 6: Updating the page with callback results
106+
---
93107

94108
**Goal**: Implement the Javascript logic to update the page's UI components based on the callback results received from the server.
95109

96110
**Tasks**:
97-
- Implement the `updateValues()` function in `exercise6/nanodash/static/index.js`
111+
- Implement the `updateValues()` Javascript function in `exercise6/nanodash/static/index.js`
98112

99113
**Files to modify**:
100114
- `exercise6/nanodash/static/index.js`
101115

102116
**Command to run tests**
103117
`python -m pytest exercise6/`
104118

105-
### Exercise 7: Write your own NanoDash Application
119+
### Exercise 7: Writing your own NanoDash application
120+
---
106121

107122
**Goal**: Use the NanoDash framework to write your own interactive dashboard. You can modify the framework or add new components if you like.
108123

File renamed without changes.

exercise1/nanodash/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
NanoDash - A lightweight dashboard framework for Python
33
"""
4+
45
from .nanodash import NanoDash
56

6-
__version__ = "0.1.0"
7+
__version__ = "0.1.0"

exercise1/nanodash/nanodash.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
"""
2+
This file contains the definition for the NanoDash class itself,
3+
including logic for running the Flask server, returning the HTML layout
4+
of the app, and handling callbacks.
5+
"""
6+
17
import flask
28

9+
310
class NanoDash:
411
def __init__(self, title: str = "NanoDash App") -> None:
512
self.app = flask.Flask(__name__)
@@ -39,11 +46,13 @@ def full_html(self) -> str:
3946
'''
4047
"""
4148
## EXERCISE 1 START
42-
raise NotImplementedError()
49+
raise NotImplementedError(
50+
"The full_html() method of the NanoDash class is not implemented yet!"
51+
)
4352
## EXERCISE 1 END
4453

45-
def run(self) -> None:
54+
def run(self, debug=True, **kwargs) -> None:
4655
"""
4756
Run the NanoDash application by starting the Flask server.
4857
"""
49-
self.app.run()
58+
self.app.run(debug=debug, **kwargs)

exercise1/test_exercise_1.py

+18-10
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,28 @@
22
Exercise 1: Testing the basic Flask server with static HTML capabilities
33
"""
44
import requests
5-
from selenium.webdriver.common.by import By
65
import pytest
7-
from test_utils import start_server, app_test_context
6+
7+
from exercise1.app import app
8+
from test_utils import start_app
9+
10+
11+
# Set up the tests by launching the test app in another thread
12+
# Will run once at the start of the test file
13+
@pytest.fixture(scope="module", autouse=True)
14+
def setup_module():
15+
start_app(app)
16+
return
817

918
def test_page_structure():
1019
"""Test if the Flask server is running and returning HTML content."""
11-
server_thread = start_server("exercise1/app.py")
20+
21+
# Check if the server is running and responding to requests
1222
response = requests.get("http://127.0.0.1:5000")
13-
assert response.status_code == 200
14-
assert "<html" in response.text, "Response does not contain HTML"
23+
assert response.status_code == 200, "Request to the server failed"
1524

16-
"""Test if the base HTML structure is correct."""
17-
with app_test_context("exercise1/app.py") as driver:
18-
# Check for basic page elements
19-
assert driver.title, "Page should have a title"
20-
25+
# Check if the response contains HTML content
26+
assert "<html>" in response.text, "Response does not contain HTML"
2127

28+
# Check if the page contains a title
29+
assert "<title>" in response.text, "Response does not contain a title"

exercise2/app.py

+44-18
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,60 @@
11
try:
22
from nanodash import NanoDash
3-
from nanodash.components import Header, TextInput, Dropdown, Page
3+
from nanodash.components import Dropdown, Header, Text, Page, TextInput
44
except ModuleNotFoundError:
55
from exercise2.nanodash import NanoDash
6-
from exercise2.nanodash.components import Header, TextInput, Dropdown, Page
6+
from exercise2.nanodash.components import Dropdown, Header, Text, Page, TextInput
77

88
# Create a new Flask web server
9-
app = NanoDash()
9+
app = NanoDash(title="Exercise 2 test app")
1010

11-
# Create a header component
12-
header = Header(text="Exercise 2: TextInput and Dropdown Components")
11+
# Create page layout
12+
header = Header(text="Exercise 2: Dropdown and TextInput components")
13+
description1 = Text(
14+
text="This app tests whether the Dropdown and TextInput components are implemented correctly. "
15+
)
16+
description2 = Text(
17+
text="Below, you should see a dropdown menu containing a list of animals, and a text input field. "
18+
)
1319

14-
# Create the components needed for the test
15-
text_input = TextInput(id="input-test", value="Test Value")
20+
# Create components
21+
animal_dropdown_label = Text(text="Choose an animal:")
22+
animal_dropdown = Dropdown(
23+
id="animal-dropdown",
24+
value="Porcupine",
25+
options=[
26+
"Deer",
27+
"Bear",
28+
"Beaver",
29+
"Porcupine",
30+
"Squirrel",
31+
"Raccoon",
32+
"Fox",
33+
],
34+
)
1635

17-
dropdown = Dropdown(
18-
id="dropdown-test",
19-
options=["Option 1", "Option 2", "Option 3"],
36+
animal_textinput_label = Text(text="Or, enter another animal here:")
37+
animal_textinput = TextInput(
38+
id="animal-textinput",
39+
value="Your favorite animal",
2040
)
2141

22-
# Create a page with the components
23-
page = Page(children=[
24-
header,
25-
text_input,
26-
dropdown
27-
])
42+
# Create the page layout with all components
43+
page = Page(
44+
children=[
45+
header,
46+
description1,
47+
description2,
48+
animal_dropdown_label,
49+
animal_dropdown,
50+
animal_textinput_label,
51+
animal_textinput,
52+
]
53+
)
2854

2955
# Add layout to the app
3056
app.set_layout(page)
3157

32-
# Run the app if this file is executed directly
58+
# Run the app
3359
if __name__ == "__main__":
34-
app.run()
60+
app.run()

exercise2/nanodash/__init__.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
"""
22
NanoDash - A lightweight dashboard framework for Python
33
"""
4+
45
from .nanodash import NanoDash
5-
from .components import (
6-
Header,
7-
TextInput,
8-
Button,
9-
Dropdown,
10-
Page,
11-
)
126

13-
__version__ = "0.1.0"
7+
__version__ = "0.1.0"

0 commit comments

Comments
 (0)