Skip to content

Commit b8bac9b

Browse files
committed
Refactor calculator tutorial and add integration test
Refactored the calculator tutorial documentation to use code includes for examples, updated code snippets for clarity, and improved button class definitions. Added an __init__.py to the calculator example directory and introduced an integration test for the basic calculator example. Also updated calc1.py to use a main guard for script execution.
1 parent 0e20c25 commit b8bac9b

File tree

4 files changed

+146
-158
lines changed

4 files changed

+146
-158
lines changed

sdk/python/examples/tutorials/calculator/__init__.py

Whitespace-only changes.

sdk/python/examples/tutorials/calculator/calc1.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ def main(page: ft.Page):
2929
)
3030

3131

32-
ft.run(main)
32+
if __name__ == "__main__":
33+
ft.run(main)

sdk/python/packages/flet/docs/tutorials/calculator.md

Lines changed: 71 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
title: Calculator Tutorial
3+
examples: ../../examples/tutorials/calculator
34
---
45

56
In this tutorial you will learn, step-by-step, how to create a Calculator app in
6-
Python using
7-
Flet framework and publish it as a desktop, mobile or web app.
7+
Python using Flet framework and publish it as a desktop, mobile or web app.
88
The app is a simple console program, yet it is a multi-platform application with
99
similar to iPhone calculator app UI:
1010

@@ -56,37 +56,8 @@ and a few [`Button`][flet.Button]s with all the numbers and actions on them.
5656

5757
Create `calc.py` with the following contents:
5858

59-
```python title="calc.py"
60-
import flet as ft
61-
62-
def main(page: ft.Page):
63-
page.title = "Calc App"
64-
result = ft.Text(value="0")
65-
66-
page.add(
67-
result,
68-
ft.Button(text="AC"),
69-
ft.Button(text="+/-"),
70-
ft.Button(text="%"),
71-
ft.Button(text="/"),
72-
ft.Button(text="7"),
73-
ft.Button(text="8"),
74-
ft.Button(text="9"),
75-
ft.Button(text="*"),
76-
ft.Button(text="4"),
77-
ft.Button(text="5"),
78-
ft.Button(text="6"),
79-
ft.Button(text="-"),
80-
ft.Button(text="1"),
81-
ft.Button(text="2"),
82-
ft.Button(text="3"),
83-
ft.Button(text="+"),
84-
ft.Button(text="0"),
85-
ft.Button(text="."),
86-
ft.Button(text="="),
87-
)
88-
89-
ft.run(main)
59+
```python
60+
--8<-- "{{ examples }}/calc1.py"
9061
```
9162

9263
Run the app and you should see a page like this:
@@ -100,58 +71,8 @@ Now let's arrange the text and buttons in 6 horizontal [`Row`][flet.Row]s.
10071

10172
Replace `calc.py` contents with the following:
10273

103-
```python title="calc.py"
104-
import flet as ft
105-
106-
107-
def main(page: ft.Page):
108-
page.title = "Calc App"
109-
result = ft.Text(value="0")
110-
111-
page.add(
112-
ft.Row(controls=[result]),
113-
ft.Row(
114-
controls=[
115-
ft.Button(text="AC"),
116-
ft.Button(text="+/-"),
117-
ft.Button(text="%"),
118-
ft.Button(text="/"),
119-
]
120-
),
121-
ft.Row(
122-
controls=[
123-
ft.Button(text="7"),
124-
ft.Button(text="8"),
125-
ft.Button(text="9"),
126-
ft.Button(text="*"),
127-
]
128-
),
129-
ft.Row(
130-
controls=[
131-
ft.Button(text="4"),
132-
ft.Button(text="5"),
133-
ft.Button(text="6"),
134-
ft.Button(text="-"),
135-
]
136-
),
137-
ft.Row(
138-
controls=[
139-
ft.Button(text="1"),
140-
ft.Button(text="2"),
141-
ft.Button(text="3"),
142-
ft.Button(text="+"),
143-
]
144-
),
145-
ft.Row(
146-
controls=[
147-
ft.Button(text="0"),
148-
ft.Button(text="."),
149-
ft.Button(text="="),
150-
]
151-
),
152-
)
153-
154-
ft.run(main)
74+
```python
75+
--8<-- "{{ examples }}/calc2.py"
15576
```
15677

15778
Run the app and you should see a page like this:
@@ -175,7 +96,7 @@ Here is the code for adding the container to the page:
17596
ft.Container(
17697
width=350,
17798
bgcolor=ft.Colors.BLACK,
178-
border_radius=ft.border_radius.all(20),
99+
border_radius=ft.BorderRadius.all(20),
179100
padding=20,
180101
content=ft.Column(
181102
controls= [], # (1)!
@@ -196,91 +117,95 @@ result = ft.Text(value="0", color=ft.Colors.WHITE, size=20)
196117
```
197118

198119
For the buttons, if we look again at the UI we are aiming to achieve, there are 3 types of buttons:
120+
199121
1. **Digit Buttons**. They have dark grey background color and white text, size is the same for all.
122+
200123
2. **Action Buttons**. They have orange background color and white text, size is the same for all except `0` button which is twice as large.
124+
201125
3. **Extra action buttons**. They have light grey background color and dark text, size is the same for all.
202126

203127
The buttons will be used multiple time in the program, so we will be creating
204128
custom [Styled Controls](../cookbook/custom-controls.md#styled-controls) to reuse the code.
205129

206130
Since all those types should inherit from `Button` class and have common `text` and `expand` properties, let's create a parent `CalcButton` class:
131+
207132
```python
133+
@ft.control
208134
class CalcButton(ft.Button):
209-
def __init__(self, text, expand=1):
210-
super().__init__()
211-
self.text = text
212-
self.expand = expand
135+
expand: int = 1
213136
```
214137

215138
Now let's create child classes for all three types of buttons:
216139

217140
```python
141+
@ft.control
218142
class DigitButton(CalcButton):
219-
def __init__(self, text, expand=1):
220-
CalcButton.__init__(self, text, expand)
221-
self.bgcolor = ft.Colors.WHITE_24
222-
self.color = ft.Colors.WHITE
143+
bgcolor: ft.Colors = ft.Colors.WHITE_24
144+
color: ft.Colors = ft.Colors.WHITE
145+
223146

147+
@ft.control
224148
class ActionButton(CalcButton):
225-
def __init__(self, text):
226-
CalcButton.__init__(self, text)
227-
self.bgcolor = ft.Colors.ORANGE
228-
self.color = ft.Colors.WHITE
149+
bgcolor: ft.Colors = ft.Colors.ORANGE
150+
color: ft.Colors = ft.Colors.WHITE
229151

152+
153+
@ft.control
230154
class ExtraActionButton(CalcButton):
231-
def __init__(self, text):
232-
CalcButton.__init__(self, text)
233-
self.bgcolor = ft.Colors.BLUE_GREY_100
234-
self.color = ft.Colors.BLACK
155+
bgcolor: ft.Colors = ft.Colors.BLUE_GREY_100
156+
color: ft.Colors = ft.Colors.BLACK
235157
```
236158

237159
We will be using these new classes now to create rows of buttons in the Container:
238160

239161
```python
240-
content=ft.Column(
241-
controls=[
242-
ft.Row(controls=[result], alignment="end"),
243-
ft.Row(
244-
controls=[
245-
ExtraActionButton(text="AC"),
246-
ExtraActionButton(text="+/-"),
247-
ExtraActionButton(text="%"),
248-
ActionButton(text="/"),
249-
]
250-
),
251-
ft.Row(
162+
content = ft.Column(
252163
controls=[
253-
DigitButton(text="7"),
254-
DigitButton(text="8"),
255-
DigitButton(text="9"),
256-
ActionButton(text="*"),
164+
ft.Row(
165+
controls=[self.result],
166+
alignment=ft.MainAxisAlignment.END,
167+
),
168+
ft.Row(
169+
controls=[
170+
ExtraActionButton(content="AC"),
171+
ExtraActionButton(content="+/-"),
172+
ExtraActionButton(content="%"),
173+
ActionButton(content="/"),
174+
]
175+
),
176+
ft.Row(
177+
controls=[
178+
DigitButton(content="7"),
179+
DigitButton(content="8"),
180+
DigitButton(content="9"),
181+
ActionButton(content="*"),
182+
]
183+
),
184+
ft.Row(
185+
controls=[
186+
DigitButton(content="4"),
187+
DigitButton(content="5"),
188+
DigitButton(content="6"),
189+
ActionButton(content="-"),
190+
]
191+
),
192+
ft.Row(
193+
controls=[
194+
DigitButton(content="1"),
195+
DigitButton(content="2"),
196+
DigitButton(content="3"),
197+
ActionButton(content="+"),
198+
]
199+
),
200+
ft.Row(
201+
controls=[
202+
DigitButton(content="0", expand=2),
203+
DigitButton(content="."),
204+
ActionButton(content="="),
205+
]
206+
),
257207
]
258-
),
259-
ft.Row(
260-
controls=[
261-
DigitButton(text="4"),
262-
DigitButton(text="5"),
263-
DigitButton(text="6"),
264-
ActionButton(text="-"),
265-
]
266-
),
267-
ft.Row(
268-
controls=[
269-
DigitButton(text="1"),
270-
DigitButton(text="2"),
271-
DigitButton(text="3"),
272-
ActionButton(text="+"),
273-
]
274-
),
275-
ft.Row(
276-
controls=[
277-
DigitButton(text="0", expand=2),
278-
DigitButton(text="."),
279-
ActionButton(text="="),
280-
]
281-
),
282-
]
283-
),
208+
)
284209
```
285210

286211
/// details | Full code
@@ -329,19 +254,8 @@ page.add(calc1, calc2)
329254
## Handling events
330255

331256
Now let's make the calculator do its job. We will be using the same event handler
332-
for all the buttons and use `data` property to differentiate between the actions
333-
depending on the button clicked. For `CalcButton` class, let's specify `on_click=button_clicked`
334-
event and set `data` property equal to button's text:
335-
336-
```python
337-
class CalcButton(ft.Button):
338-
def __init__(self, text, button_clicked, expand=1):
339-
super().__init__()
340-
self.text = text
341-
self.expand = expand
342-
self.on_click = button_clicked
343-
self.data = text
344-
```
257+
for all the buttons and use `content` property to differentiate between the actions
258+
depending on the button clicked.
345259

346260
We will define `button_click` method in `CalculatorClass` and pass it to each button.
347261
Below is `on_click` event handler that will reset the Text value when "AC" button is clicked:
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import pytest
2+
3+
import flet.testing as ftt
4+
5+
# from examples.controls.checkbox import basic, handling_events, styled
6+
from examples.tutorials.calculator import calc1
7+
8+
# @pytest.mark.asyncio(loop_scope="function")
9+
# async def test_image_for_docs(flet_app_function: ftt.FletTestApp, request):
10+
# flet_app_function.page.theme_mode = ft.ThemeMode.LIGHT
11+
# await flet_app_function.assert_control_screenshot(
12+
# request.node.name,
13+
# ft.Column(
14+
# intrinsic_width=True,
15+
# controls=[
16+
# ft.Checkbox(),
17+
# ft.Checkbox(label="Checked", value=True),
18+
# ft.Checkbox(label="Disabled", disabled=True),
19+
# ],
20+
# ),
21+
# )
22+
23+
24+
@pytest.mark.parametrize(
25+
"flet_app_function",
26+
[{"flet_app_main": calc1.main}],
27+
indirect=True,
28+
)
29+
@pytest.mark.asyncio(loop_scope="function")
30+
async def test_basic(flet_app_function: ftt.FletTestApp):
31+
# button = await flet_app_function.tester.find_by_text("Submit")
32+
# await flet_app_function.tester.tap(button)
33+
await flet_app_function.tester.pump_and_settle()
34+
flet_app_function.assert_screenshot(
35+
"calc1",
36+
await flet_app_function.take_page_controls_screenshot(),
37+
)
38+
39+
40+
# @pytest.mark.parametrize(
41+
# "flet_app_function",
42+
# [{"flet_app_main": handling_events.main}],
43+
# indirect=True,
44+
# )
45+
# @pytest.mark.asyncio(loop_scope="function")
46+
# async def test_handling_events(flet_app_function: ftt.FletTestApp):
47+
# checkbox = await flet_app_function.tester.find_by_text(
48+
# "Checkbox with 'change' event"
49+
# )
50+
# await flet_app_function.tester.tap(checkbox)
51+
# await flet_app_function.tester.pump_and_settle()
52+
# await flet_app_function.tester.tap(checkbox)
53+
# await flet_app_function.tester.pump_and_settle()
54+
# await flet_app_function.tester.tap(checkbox)
55+
# await flet_app_function.tester.pump_and_settle()
56+
# scr = await flet_app_function.wrap_page_controls_in_screenshot()
57+
# flet_app_function.assert_screenshot(
58+
# "handling_events",
59+
# await scr.capture(pixel_ratio=flet_app_function.screenshots_pixel_ratio),
60+
# )
61+
62+
63+
# @pytest.mark.parametrize(
64+
# "flet_app_function",
65+
# [{"flet_app_main": styled.main}],
66+
# indirect=True,
67+
# )
68+
# @pytest.mark.asyncio(loop_scope="function")
69+
# async def test_styled(flet_app_function: ftt.FletTestApp):
70+
# flet_app_function.assert_screenshot(
71+
# "styled_checkboxes",
72+
# await flet_app_function.take_page_controls_screenshot(),
73+
# )

0 commit comments

Comments
 (0)