pyjs-examples 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. pyjs_examples/__init__.py +1 -0
  2. pyjs_examples/calculator/README.md +55 -0
  3. pyjs_examples/calculator/__init__.py +1 -0
  4. pyjs_examples/calculator/__main__.py +9 -0
  5. pyjs_examples/calculator/calculator.py +325 -0
  6. pyjs_examples/calculator/tests/test_browser_calculator.py +67 -0
  7. pyjs_examples/calculator/tests/test_calculator.py +72 -0
  8. pyjs_examples/catalog.py +1433 -0
  9. pyjs_examples/chatroom/README.md +50 -0
  10. pyjs_examples/chatroom/__init__.py +1 -0
  11. pyjs_examples/chatroom/__main__.py +12 -0
  12. pyjs_examples/chatroom/chat_server.py +156 -0
  13. pyjs_examples/chatroom/chatroom.py +704 -0
  14. pyjs_examples/chatroom/tests/test_browser_chatroom.py +59 -0
  15. pyjs_examples/chatroom/tests/test_chat_server.py +53 -0
  16. pyjs_examples/chatroom/visual_test.py +156 -0
  17. pyjs_examples/counter.py +73 -0
  18. pyjs_examples/kanban/README.md +31 -0
  19. pyjs_examples/kanban/__init__.py +1 -0
  20. pyjs_examples/kanban/__main__.py +8 -0
  21. pyjs_examples/kanban/kanban.py +581 -0
  22. pyjs_examples/kanban/tests/renders/kanban-board.png +0 -0
  23. pyjs_examples/kanban/tests/test_browser_kanban.py +150 -0
  24. pyjs_examples/kanban/tests/test_kanban.py +38 -0
  25. pyjs_examples/kanban/visual_test.py +108 -0
  26. pyjs_examples/nativescript_counter/README.md +31 -0
  27. pyjs_examples/nativescript_counter/__init__.py +5 -0
  28. pyjs_examples/nativescript_counter/__main__.py +17 -0
  29. pyjs_examples/nativescript_counter/counter.py +54 -0
  30. pyjs_examples/nativescript_counter/tests/test_android_counter.py +31 -0
  31. pyjs_examples/nativescript_counter/tests/test_counter.py +11 -0
  32. pyjs_examples/samples/__init__.py +1 -0
  33. pyjs_examples/samples/state_counter/README.md +19 -0
  34. pyjs_examples/samples/state_counter/__init__.py +1 -0
  35. pyjs_examples/samples/state_counter/__main__.py +3 -0
  36. pyjs_examples/samples/state_counter/state_counter.py +60 -0
  37. pyjs_examples/samples/state_counter/tests/test_browser_state_counter.py +18 -0
  38. pyjs_examples/samples/state_counter/tests/test_state_counter.py +17 -0
  39. pyjs_examples/samples/ui_dialog/README.md +19 -0
  40. pyjs_examples/samples/ui_dialog/__init__.py +1 -0
  41. pyjs_examples/samples/ui_dialog/__main__.py +3 -0
  42. pyjs_examples/samples/ui_dialog/tests/test_browser_ui_dialog.py +21 -0
  43. pyjs_examples/samples/ui_dialog/tests/test_ui_dialog.py +10 -0
  44. pyjs_examples/samples/ui_dialog/ui_dialog.py +65 -0
  45. pyjs_examples/space_invaders/README.md +45 -0
  46. pyjs_examples/space_invaders/__init__.py +1 -0
  47. pyjs_examples/space_invaders/__main__.py +3 -0
  48. pyjs_examples/space_invaders/space_invaders.py +1025 -0
  49. pyjs_examples/space_invaders/tests/renders/action_scene.png +0 -0
  50. pyjs_examples/space_invaders/tests/renders/initial_wave.png +0 -0
  51. pyjs_examples/space_invaders/tests/test_browser_game.py +86 -0
  52. pyjs_examples/space_invaders/tests/test_game.py +83 -0
  53. pyjs_examples/todomvc/README.md +50 -0
  54. pyjs_examples/todomvc/__init__.py +1 -0
  55. pyjs_examples/todomvc/__main__.py +12 -0
  56. pyjs_examples/todomvc/base.css +141 -0
  57. pyjs_examples/todomvc/index.css +393 -0
  58. pyjs_examples/todomvc/tests/test_browser_todomvc.py +145 -0
  59. pyjs_examples/todomvc/tests/test_todomvc.py +84 -0
  60. pyjs_examples/todomvc/todomvc.py +426 -0
  61. pyjs_examples/workspace/README.md +25 -0
  62. pyjs_examples/workspace/__init__.py +1 -0
  63. pyjs_examples/workspace/__main__.py +3 -0
  64. pyjs_examples/workspace/tests/test_browser_workspace.py +109 -0
  65. pyjs_examples/workspace/tests/test_workspace.py +27 -0
  66. pyjs_examples/workspace/workspace.py +749 -0
  67. pyjs_examples-0.0.1.dist-info/METADATA +71 -0
  68. pyjs_examples-0.0.1.dist-info/RECORD +71 -0
  69. pyjs_examples-0.0.1.dist-info/WHEEL +5 -0
  70. pyjs_examples-0.0.1.dist-info/entry_points.txt +11 -0
  71. pyjs_examples-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,150 @@
1
+ import pytest
2
+ from pyjs import js
3
+ from pyjs.testing.web import assert_render, mount
4
+ from pyjs.web.dom import Element
5
+ from pyjs.web.dragdrop import DataTransfer, DragEvent
6
+ from pyjs.web.events import Event
7
+ from pyjs_examples.kanban.kanban import KanbanBoard, KanbanCard, KanbanColumn
8
+
9
+ pytestmark = pytest.mark.pyjs_web
10
+
11
+
12
+ @js
13
+ def mounted_board() -> KanbanBoard:
14
+ board = KanbanBoard()
15
+ mount(board)
16
+ return board
17
+
18
+
19
+ @js
20
+ def card_order(column: KanbanColumn) -> str:
21
+ card_ids: list[str] = []
22
+ for card in column.cards.children:
23
+ card_id = card.getAttribute("data-card-id")
24
+ if card_id is not None:
25
+ card_ids.append(card_id)
26
+ return ",".join(card_ids)
27
+
28
+
29
+ @js
30
+ def drag_to(source: KanbanCard, target: Element, client_y: int = 0):
31
+ transfer = DataTransfer()
32
+ source.dispatchEvent(
33
+ DragEvent(
34
+ "dragstart",
35
+ bubbles=True,
36
+ cancelable=True,
37
+ dataTransfer=transfer,
38
+ )
39
+ )
40
+ target.dispatchEvent(
41
+ DragEvent(
42
+ "dragover",
43
+ bubbles=True,
44
+ cancelable=True,
45
+ clientY=client_y,
46
+ dataTransfer=transfer,
47
+ )
48
+ )
49
+ target.dispatchEvent(
50
+ DragEvent(
51
+ "drop",
52
+ bubbles=True,
53
+ cancelable=True,
54
+ clientY=client_y,
55
+ dataTransfer=transfer,
56
+ )
57
+ )
58
+ source.dispatchEvent(DragEvent("dragend", bubbles=True, dataTransfer=transfer))
59
+
60
+
61
+ @js
62
+ def drop_before(source: KanbanCard, target: KanbanCard):
63
+ bounds = target.getBoundingClientRect()
64
+ drag_to(source, target, round(bounds.top + 1))
65
+
66
+
67
+ @js
68
+ def drop_after(source: KanbanCard, target: KanbanCard):
69
+ bounds = target.getBoundingClientRect()
70
+ drag_to(source, target, round(bounds.bottom - 1))
71
+
72
+
73
+ @js
74
+ def test_drag_moves_a_card_between_columns_and_updates_counts():
75
+ board = mounted_board()
76
+
77
+ drag_to(board.onboarding_card, board.in_progress.cards)
78
+
79
+ assert card_order(board.todo) == "launch-checklist,customer-interview"
80
+ assert card_order(board.in_progress) == "drag-drop,empty-states,onboarding-audit"
81
+ assert board.todo.count.textContent == "2"
82
+ assert board.in_progress.count.textContent == "3"
83
+ assert board.onboarding_card.getAttribute("data-dragging") is None
84
+ assert not board.in_progress.cards.hasAttribute("data-drag-over")
85
+ assert board.status.textContent == "Audit onboarding flow moved to In progress"
86
+
87
+
88
+ @js
89
+ def test_drop_position_reorders_before_and_after_cards():
90
+ board = mounted_board()
91
+
92
+ drop_before(board.interview_card, board.onboarding_card)
93
+ assert card_order(board.todo) == (
94
+ "customer-interview,onboarding-audit,launch-checklist"
95
+ )
96
+
97
+ drop_after(board.interview_card, board.checklist_card)
98
+ assert card_order(board.todo) == (
99
+ "onboarding-audit,launch-checklist,customer-interview"
100
+ )
101
+
102
+
103
+ @js
104
+ def test_invalid_and_self_drops_leave_the_board_unchanged():
105
+ board = mounted_board()
106
+ initial_todo = card_order(board.todo)
107
+ initial_done = card_order(board.done)
108
+
109
+ drag_to(board.onboarding_card, board.onboarding_card)
110
+
111
+ invalid = DataTransfer()
112
+ invalid.setData("application/x-pyjs-kanban-card", "missing-card")
113
+ board.done.cards.dispatchEvent(
114
+ DragEvent("drop", bubbles=True, cancelable=True, dataTransfer=invalid)
115
+ )
116
+ board.done.cards.dispatchEvent(DragEvent("drop", bubbles=True, cancelable=True))
117
+
118
+ assert card_order(board.todo) == initial_todo
119
+ assert card_order(board.done) == initial_done
120
+ assert board.todo.count.textContent == "3"
121
+ assert board.done.count.textContent == "2"
122
+
123
+
124
+ @js
125
+ def test_arrow_controls_move_and_reorder_cards_without_dragging():
126
+ board = mounted_board()
127
+
128
+ board.drag_card.move_down_button.dispatchEvent(Event("click"))
129
+ assert card_order(board.in_progress) == "empty-states,drag-drop"
130
+ assert board.drag_card.move_down_button.hasAttribute("disabled")
131
+ assert not board.drag_card.move_up_button.hasAttribute("disabled")
132
+
133
+ board.drag_card.move_left_button.dispatchEvent(Event("click"))
134
+ assert card_order(board.todo) == (
135
+ "onboarding-audit,launch-checklist,customer-interview,drag-drop"
136
+ )
137
+ assert card_order(board.in_progress) == "empty-states"
138
+ assert board.todo.count.textContent == "4"
139
+ assert board.in_progress.count.textContent == "1"
140
+ assert board.drag_card.move_left_button.hasAttribute("disabled")
141
+
142
+
143
+ @js
144
+ def test_kanban_visual_showcase():
145
+ board = mounted_board()
146
+ drag_to(board.onboarding_card, board.in_progress.cards)
147
+
148
+ assert card_order(board.in_progress) == "drag-drop,empty-states,onboarding-audit"
149
+ mount(board.columns)
150
+ assert_render(board.columns, "kanban-board", pixel_threshold=10)
@@ -0,0 +1,38 @@
1
+ import pytest
2
+ from pyjs.web.ssr import render_html
3
+ from pyjs_examples.kanban.kanban import KanbanBoard, KanbanCard, main
4
+
5
+
6
+ @pytest.mark.pyjs_python
7
+ def test_board_renders_dom_owned_columns_and_cards():
8
+ board = main()
9
+
10
+ assert isinstance(board, KanbanBoard)
11
+ assert [len(column.cards.children) for column in (board.todo, board.in_progress, board.done)] == [
12
+ 3,
13
+ 2,
14
+ 2,
15
+ ]
16
+ count_html = render_html(board.todo.count)
17
+ assert 'data-column-count=""' in count_html
18
+ assert 'aria-label="Todo card count"' in count_html
19
+ assert count_html.endswith(">3</span>")
20
+
21
+ first = board.todo.cards.children[0]
22
+ assert isinstance(first, KanbanCard)
23
+ assert first.getAttribute("data-card-id") == "onboarding-audit"
24
+ assert first.getAttribute("draggable") == "true"
25
+ assert first.move_left_button.hasAttribute("disabled")
26
+ assert first.move_up_button.hasAttribute("disabled")
27
+ assert not first.move_right_button.hasAttribute("disabled")
28
+
29
+
30
+ @pytest.mark.pyjs_python
31
+ def test_main_board_contains_accessible_drag_alternatives():
32
+ board = KanbanBoard()
33
+
34
+ assert board.drag_card.move_left_button.getAttribute("aria-label") == (
35
+ "Move Build drag-and-drop left"
36
+ )
37
+ assert board.drag_card.move_up_button.getAttribute("title") == "Move up"
38
+ assert board.status.getAttribute("aria-live") == "polite"
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+ from pyjs.tooling import stop_process
12
+ from pyjs.tooling.browser import Browser, BrowserPage, free_port, wait_for_http
13
+
14
+ EXAMPLE_ROOT = Path(__file__).resolve().parent
15
+ OUTPUT = Path.cwd() / "screenshots"
16
+
17
+
18
+ async def board_state(page: BrowserPage) -> dict:
19
+ return {
20
+ "todo": await page.attributes('[data-testid="todo-cards"] > [data-card-id]', "data-card-id"),
21
+ "inProgress": await page.attributes(
22
+ '[data-testid="in-progress-cards"] > [data-card-id]', "data-card-id"
23
+ ),
24
+ "done": await page.attributes('[data-testid="done-cards"] > [data-card-id]', "data-card-id"),
25
+ "counts": await page.texts("[data-column-count]"),
26
+ "status": await page.text("[data-board-status]"),
27
+ "activeDropZones": await page.attributes("[data-drag-over]", "data-testid"),
28
+ }
29
+
30
+
31
+ async def exercise(page: BrowserPage) -> dict:
32
+ await page.wait_for_selector("kanban-board", "the hydrated Kanban board")
33
+ await page.drag(
34
+ '[data-card-id="onboarding-audit"]',
35
+ '[data-card-id="drag-drop"]',
36
+ target_y_ratio=0.2,
37
+ )
38
+ await page.wait_for(
39
+ "document.querySelector('[data-testid=\"in-progress-cards\"]')"
40
+ "?.firstElementChild?.getAttribute('data-card-id') === 'onboarding-audit'",
41
+ "the card to move through Chrome's native drag lifecycle",
42
+ )
43
+ await page.drag(
44
+ '[data-card-id="launch-checklist"]',
45
+ '[data-testid="done-cards"]',
46
+ target_y_ratio=0.95,
47
+ )
48
+ await page.wait_for(
49
+ "document.querySelector('[data-testid=\"done-cards\"]')"
50
+ "?.lastElementChild?.getAttribute('data-card-id') === 'launch-checklist'",
51
+ "a card dropped in the column's empty space",
52
+ )
53
+
54
+ result = await board_state(page)
55
+ expected = {
56
+ "todo": ["customer-interview"],
57
+ "inProgress": ["onboarding-audit", "drag-drop", "empty-states"],
58
+ "done": ["scope-release", "browser-runner", "launch-checklist"],
59
+ "counts": ["1", "3", "3"],
60
+ "status": "Write launch checklist moved to Done",
61
+ "activeDropZones": [],
62
+ }
63
+ if result != expected:
64
+ raise RuntimeError(f"Unexpected board state: {json.dumps(result)}")
65
+ if page.exceptions:
66
+ raise RuntimeError(f"Browser errors: {'; '.join(page.exceptions)}")
67
+
68
+ await page.settle()
69
+ await page.screenshot(OUTPUT / "kanban.png", selector="[data-kanban-board]")
70
+ return result
71
+
72
+
73
+ async def main() -> None:
74
+ port = free_port()
75
+ url = f"http://127.0.0.1:{port}/"
76
+ environment = os.environ.copy()
77
+ environment["PYJS_PORT"] = str(port)
78
+ with tempfile.TemporaryFile(mode="w+") as server_log:
79
+ server = subprocess.Popen(
80
+ [sys.executable, "kanban.py"],
81
+ cwd=EXAMPLE_ROOT,
82
+ env=environment,
83
+ stdout=server_log,
84
+ stderr=subprocess.STDOUT,
85
+ )
86
+ try:
87
+ await wait_for_http(url, timeout=20)
88
+ async with Browser() as browser:
89
+ page = await browser.new_page(url)
90
+ await page.set_viewport(1440, 1000)
91
+ result = await exercise(page)
92
+ OUTPUT.mkdir(parents=True, exist_ok=True)
93
+ (OUTPUT / "kanban.json").write_text(
94
+ json.dumps(result, indent=2) + "\n",
95
+ encoding="utf-8",
96
+ )
97
+ print(json.dumps(result, indent=2))
98
+ except BaseException:
99
+ stop_process(server)
100
+ server_log.seek(0)
101
+ sys.stderr.write(server_log.read())
102
+ raise
103
+ finally:
104
+ stop_process(server)
105
+
106
+
107
+ if __name__ == "__main__":
108
+ asyncio.run(main())
@@ -0,0 +1,31 @@
1
+ # NativeScript Counter
2
+
3
+ A minimal native Android application written in Python and styled with Tailwind CSS. PyJS
4
+ transpiles the application and manages its private Node and NativeScript toolchain outside the
5
+ source tree.
6
+
7
+ With an Android SDK and at least one virtual device installed:
8
+
9
+ ```bash
10
+ python -m pyjs_examples.nativescript_counter
11
+ ```
12
+
13
+ The first run downloads the managed Node and NativeScript tools and creates a generated native
14
+ build workspace under `~/.cache/pyjs/`.
15
+
16
+ Run the host-side unit test normally:
17
+
18
+ ```bash
19
+ python -m pytest -q examples/nativescript_counter/tests
20
+ ```
21
+
22
+ Run the Android UI test on an existing emulator, or name an AVD for PyJS to start:
23
+
24
+ ```bash
25
+ python -m pytest -q examples/nativescript_counter/tests \
26
+ --pyjs-target=android --pyjs-android-avd=Routine_Test
27
+ ```
28
+
29
+ The UI test launches the generated app, finds controls by NativeScript `testID`, taps the real
30
+ button, and waits for the native label to update. It retains a screenshot, Android UI hierarchy,
31
+ app-scoped logcat, and JSON artifact manifest under `.pytest_cache/pyjs/artifacts/android/`.
@@ -0,0 +1,5 @@
1
+ """A minimal Android counter written in Python and rendered by NativeScript."""
2
+
3
+ from .counter import Counter, create_page, main
4
+
5
+ __all__ = ["Counter", "create_page", "main"]
@@ -0,0 +1,17 @@
1
+ from pyjs.tooling.nativescript import NativeScriptProject
2
+
3
+ from .counter import main
4
+
5
+
6
+ def run() -> None:
7
+ project = NativeScriptProject(
8
+ main,
9
+ name="PyJS Counter",
10
+ app_id="org.pyjs.counter",
11
+ )
12
+ device = project.run_android()
13
+ print(f"PyJS Counter is running on {device.identifier}.")
14
+
15
+
16
+ if __name__ == "__main__":
17
+ run()
@@ -0,0 +1,54 @@
1
+ from pyjs import js
2
+ from pyjs.nativescript import Application, Button, EventData, Label, Page, StackLayout
3
+
4
+
5
+ @js
6
+ class Counter:
7
+ def __init__(self):
8
+ self.count = 0
9
+
10
+ self.value = Label()
11
+ self.value.id = "counter-value"
12
+ self.value.testID = "counter-value"
13
+ self.value.text = "0"
14
+ self.value.className = "mb-6 text-center text-6xl font-bold text-slate-50"
15
+
16
+ self.button = Button()
17
+ self.button.id = "increment-button"
18
+ self.button.testID = "increment-button"
19
+ self.button.text = "Increment"
20
+ self.button.className = "mx-8 h-14 rounded-lg bg-sky-400 text-xl font-semibold text-sky-950"
21
+ self.button.on(Button.tapEvent, self.increment)
22
+
23
+ def increment(self, event: EventData):
24
+ self.count += 1
25
+ self.value.text = str(self.count)
26
+
27
+ def page(self) -> Page:
28
+ content = StackLayout()
29
+ content.className = "bg-slate-950 p-8"
30
+ content.verticalAlignment = "middle"
31
+
32
+ title = Label()
33
+ title.text = "PyJS + NativeScript"
34
+ title.className = "mb-4 text-center text-2xl font-bold text-sky-300"
35
+
36
+ content.addChild(title)
37
+ content.addChild(self.value)
38
+ content.addChild(self.button)
39
+
40
+ page = Page()
41
+ page.actionBarHidden = True
42
+ page.className = "bg-slate-950"
43
+ page.content = content
44
+ return page
45
+
46
+
47
+ @js
48
+ def create_page() -> Page:
49
+ return Counter().page()
50
+
51
+
52
+ @js
53
+ def main():
54
+ Application.run(create_page)
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+ from pyjs.testing.android import AndroidTestSession
5
+ from pyjs.tooling.android import AndroidSDK
6
+ from pyjs.tooling.nativescript import NativeScriptProject
7
+ from pyjs_examples.nativescript_counter.counter import main
8
+
9
+ pytestmark = pytest.mark.pyjs_android
10
+
11
+
12
+ def test_counter_runs_on_android(request, pyjs_artifacts_dir: Path):
13
+ android = AndroidSDK()
14
+ project = NativeScriptProject(
15
+ main,
16
+ name="PyJS Counter",
17
+ app_id="org.pyjs.counter",
18
+ android=android,
19
+ )
20
+ device = project.run_android(request.config.getoption("--pyjs-android-avd"))
21
+ session = AndroidTestSession(android, device, project.app_id)
22
+ artifacts = pyjs_artifacts_dir / "android"
23
+
24
+ with session.artifacts_on_failure(artifacts, "nativescript-counter"):
25
+ session.launch()
26
+ session.wait_for_element(test_id="counter-value", text="0")
27
+ session.tap_element(test_id="increment-button")
28
+ session.wait_for_element(test_id="counter-value", text="1")
29
+ assert session.log_errors() == ""
30
+
31
+ session.capture_artifacts(artifacts, "nativescript-counter")
@@ -0,0 +1,11 @@
1
+ from pyjs_examples.nativescript_counter.counter import Counter
2
+
3
+
4
+ def test_counter_updates_label():
5
+ counter = Counter()
6
+
7
+ counter.increment(None)
8
+ counter.increment(None)
9
+
10
+ assert counter.count == 2
11
+ assert counter.value.text == "2"
@@ -0,0 +1 @@
1
+ """Small, focused PyJS samples."""
@@ -0,0 +1,19 @@
1
+ # Reactive counter
2
+
3
+ This focused PyJS sample demonstrates a state field, an effect, browser
4
+ events, server rendering, and tests in a complete runnable project.
5
+
6
+ ## Run
7
+
8
+ ```bash
9
+ python -m pyjs_examples.samples.state_counter
10
+ ```
11
+
12
+ Open <http://127.0.0.1:8000/>.
13
+
14
+ ## Test
15
+
16
+ ```bash
17
+ python -m pytest -q
18
+ python -m pytest -q --pyjs-target=web
19
+ ```
@@ -0,0 +1 @@
1
+ """A focused reactive-state sample."""
@@ -0,0 +1,3 @@
1
+ from pyjs.server import serve
2
+
3
+ serve("pyjs_examples.samples.state_counter.state_counter")
@@ -0,0 +1,60 @@
1
+ from pyjs import effect, js, state
2
+ from pyjs.web.domx import CustomElement, button, ref, tag
3
+ from pyjs.web.events import Event
4
+
5
+
6
+ @js
7
+ class Counter(CustomElement):
8
+ count = state(0)
9
+
10
+ def __init__(self):
11
+ super().__init__()
12
+ self.value = ref(tag("strong", {"style": "font-size:3rem"}, "0"))
13
+ self.decrement_button = ref(button({"type": "button"}, "−"))
14
+ self.increment_button = ref(button({"type": "button"}, "+"))
15
+ self.decrement_button.addEventListener("click", self.decrement)
16
+ self.increment_button.addEventListener("click", self.increment)
17
+ tag(
18
+ self,
19
+ tag(
20
+ "main",
21
+ {
22
+ "style": (
23
+ "min-height:100vh;display:grid;place-items:center;background:#f8fafc;"
24
+ "font:16px system-ui,sans-serif"
25
+ )
26
+ },
27
+ tag(
28
+ "section",
29
+ {
30
+ "style": (
31
+ "display:grid;grid-template-columns:3rem 8rem 3rem;align-items:center;"
32
+ "gap:1rem;text-align:center"
33
+ )
34
+ },
35
+ self.decrement_button,
36
+ self.value,
37
+ self.increment_button,
38
+ ),
39
+ ),
40
+ )
41
+
42
+ @effect(count)
43
+ def render_count(self):
44
+ self.value.textContent = str(self.count)
45
+
46
+ def decrement(self, event: Event):
47
+ self.count -= 1
48
+
49
+ def increment(self, event: Event):
50
+ self.count += 1
51
+
52
+
53
+ def main():
54
+ return Counter()
55
+
56
+
57
+ if __name__ == "__main__":
58
+ from pyjs.server import serve
59
+
60
+ serve("state_counter")
@@ -0,0 +1,18 @@
1
+ import pytest
2
+ from pyjs import js
3
+ from pyjs.web.dom import document
4
+ from pyjs.web.events import Event
5
+ from pyjs_examples.samples.state_counter.state_counter import Counter
6
+
7
+ pytestmark = pytest.mark.pyjs_web
8
+
9
+
10
+ @js
11
+ def test_counter_buttons_update_the_mounted_element():
12
+ document.body.replaceChildren()
13
+ counter = Counter()
14
+ document.body.append(counter)
15
+
16
+ counter.increment_button.dispatchEvent(Event("click"))
17
+
18
+ assert counter.value.textContent == "1"
@@ -0,0 +1,17 @@
1
+ import pytest
2
+ from pyjs import js
3
+ from pyjs.web.events import Event
4
+ from pyjs_examples.samples.state_counter.state_counter import Counter
5
+
6
+
7
+ @pytest.mark.pyjs_python
8
+ @js
9
+ def test_counter_changes_in_both_directions():
10
+ counter = Counter()
11
+
12
+ counter.increment(Event("click"))
13
+ counter.increment(Event("click"))
14
+ counter.decrement(Event("click"))
15
+
16
+ assert counter.count == 1
17
+ assert counter.value.textContent == "1"
@@ -0,0 +1,19 @@
1
+ # Dialog
2
+
3
+ This focused PyJS sample places the `pyjs.web` dialog component in a
4
+ complete runnable and tested project.
5
+
6
+ ## Run
7
+
8
+ ```bash
9
+ python -m pyjs_examples.samples.ui_dialog
10
+ ```
11
+
12
+ Open <http://127.0.0.1:8000/>.
13
+
14
+ ## Test
15
+
16
+ ```bash
17
+ python -m pytest -q
18
+ python -m pytest -q --pyjs-target=web
19
+ ```
@@ -0,0 +1 @@
1
+ """A focused accessible-dialog sample."""
@@ -0,0 +1,3 @@
1
+ from pyjs.server import serve
2
+
3
+ serve("pyjs_examples.samples.ui_dialog.ui_dialog")
@@ -0,0 +1,21 @@
1
+ import pytest
2
+ from pyjs import js
3
+ from pyjs.web.dom import document
4
+ from pyjs.web.events import Event
5
+ from pyjs_examples.samples.ui_dialog.ui_dialog import DialogSample
6
+
7
+ pytestmark = pytest.mark.pyjs_web
8
+
9
+
10
+ @js
11
+ def test_dialog_opens_and_closes():
12
+ document.body.replaceChildren()
13
+ app = DialogSample()
14
+ document.body.append(app)
15
+
16
+ app.dialog.trigger.element.dispatchEvent(Event("click"))
17
+ assert app.dialog.is_open
18
+ assert app.dialog.content.dialog_element.open
19
+
20
+ app.dialog.close()
21
+ assert not app.dialog.is_open
@@ -0,0 +1,10 @@
1
+ import pytest
2
+ from pyjs_examples.samples.ui_dialog.ui_dialog import DialogSample
3
+
4
+
5
+ @pytest.mark.pyjs_python
6
+ def test_dialog_sample_has_an_accessible_label():
7
+ app = DialogSample()
8
+
9
+ assert app.dialog.content.dialog_element.getAttribute("aria-label") == "Welcome to PyJS"
10
+ assert app.dialog.trigger.element.getAttribute("aria-haspopup") == "dialog"